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 * <p> 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * <p> 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.client; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023 024import java.io.File; 025import java.io.IOException; 026import java.nio.file.Paths; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Iterator; 030import java.util.List; 031import java.util.UUID; 032import org.apache.commons.io.FileUtils; 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.fs.FileSystem; 035import org.apache.hadoop.fs.Path; 036import org.apache.hadoop.hbase.HBaseClassTestRule; 037import org.apache.hadoop.hbase.HBaseTestingUtility; 038import org.apache.hadoop.hbase.HConstants; 039import org.apache.hadoop.hbase.HTableDescriptor; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; 042import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; 043import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 044import org.apache.hadoop.hbase.snapshot.SnapshotDoesNotExistException; 045import org.apache.hadoop.hbase.snapshot.SnapshotManifestV1; 046import org.apache.hadoop.hbase.snapshot.SnapshotManifestV2; 047import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils; 048import org.apache.hadoop.hbase.testclassification.LargeTests; 049import org.apache.hadoop.hbase.util.Bytes; 050import org.apache.hadoop.hbase.util.CommonFSUtils; 051import org.junit.After; 052import org.junit.AfterClass; 053import org.junit.Before; 054import org.junit.BeforeClass; 055import org.junit.ClassRule; 056import org.junit.Test; 057import org.junit.experimental.categories.Category; 058import org.junit.runner.RunWith; 059import org.junit.runners.Parameterized; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 064 065import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 066 067/** 068 * This class tests that the use of a temporary snapshot directory supports snapshot functionality 069 * while the temporary directory is on a different file system than the root directory 070 * <p> 071 * This is an end-to-end test for the snapshot utility 072 */ 073@Category(LargeTests.class) 074@RunWith(Parameterized.class) 075public class TestSnapshotTemporaryDirectory { 076 077 @ClassRule public static final HBaseClassTestRule CLASS_RULE = 078 HBaseClassTestRule.forClass(TestSnapshotTemporaryDirectory.class); 079 080 @Parameterized.Parameters public static Iterable<Integer> data() { 081 return Arrays 082 .asList(SnapshotManifestV1.DESCRIPTOR_VERSION, SnapshotManifestV2.DESCRIPTOR_VERSION); 083 } 084 085 @Parameterized.Parameter public int manifestVersion; 086 087 private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotTemporaryDirectory.class); 088 protected static final int NUM_RS = 2; 089 protected static String TEMP_DIR = 090 Paths.get("").toAbsolutePath().toString() + Path.SEPARATOR + UUID.randomUUID().toString(); 091 092 protected static Admin admin; 093 protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 094 protected static final String STRING_TABLE_NAME = "test"; 095 protected static final byte[] TEST_FAM = Bytes.toBytes("fam"); 096 protected static final TableName TABLE_NAME = TableName.valueOf(STRING_TABLE_NAME); 097 098 /** 099 * Setup the config for the cluster 100 * 101 * @throws Exception on failure 102 */ 103 @BeforeClass 104 public static void setupCluster() throws Exception { 105 setupConf(UTIL.getConfiguration()); 106 UTIL.startMiniCluster(NUM_RS); 107 admin = UTIL.getHBaseAdmin(); 108 } 109 110 private static void setupConf(Configuration conf) { 111 // disable the ui 112 conf.setInt("hbase.regionsever.info.port", -1); 113 // change the flush size to a small amount, regulating number of store files 114 conf.setInt("hbase.hregion.memstore.flush.size", 25000); 115 // so make sure we get a compaction when doing a load, but keep around some 116 // files in the store 117 conf.setInt("hbase.hstore.compaction.min", 10); 118 conf.setInt("hbase.hstore.compactionThreshold", 10); 119 // block writes if we get to 12 store files 120 conf.setInt("hbase.hstore.blockingStoreFiles", 12); 121 // Enable snapshot 122 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 123 conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, 124 ConstantSizeRegionSplitPolicy.class.getName()); 125 conf.set(SnapshotDescriptionUtils.SNAPSHOT_WORKING_DIR, "file://" + new Path(TEMP_DIR, ".tmpDir").toUri()); 126 } 127 128 @Before 129 public void setup() throws Exception { 130 HTableDescriptor htd = new HTableDescriptor(TABLE_NAME); 131 htd.setRegionReplication(getNumReplicas()); 132 UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration()); 133 } 134 135 protected int getNumReplicas() { 136 return 1; 137 } 138 139 @After 140 public void tearDown() throws Exception { 141 UTIL.deleteTable(TABLE_NAME); 142 SnapshotTestingUtils.deleteAllSnapshots(UTIL.getHBaseAdmin()); 143 SnapshotTestingUtils.deleteArchiveDirectory(UTIL); 144 } 145 146 @AfterClass 147 public static void cleanupTest() { 148 try { 149 UTIL.shutdownMiniCluster(); 150 FileUtils.deleteDirectory(new File(TEMP_DIR)); 151 } catch (Exception e) { 152 LOG.warn("failure shutting down cluster", e); 153 } 154 } 155 156 @Test 157 public void testRestoreDisabledSnapshot() 158 throws IOException, InterruptedException { 159 long tid = System.currentTimeMillis(); 160 TableName tableName = TableName.valueOf("testtb-" + tid); 161 byte[] emptySnapshot = Bytes.toBytes("emptySnaptb-" + tid); 162 byte[] snapshotName0 = Bytes.toBytes("snaptb0-" + tid); 163 byte[] snapshotName1 = Bytes.toBytes("snaptb1-" + tid); 164 int snapshot0Rows; 165 int snapshot1Rows; 166 167 // create Table and disable it 168 SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM); 169 admin.disableTable(tableName); 170 171 // take an empty snapshot 172 takeSnapshot(tableName, Bytes.toString(emptySnapshot), true); 173 174 // enable table and insert data 175 admin.enableTable(tableName); 176 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 177 try (Table table = UTIL.getConnection().getTable(tableName)) { 178 snapshot0Rows = UTIL.countRows(table); 179 } 180 admin.disableTable(tableName); 181 182 // take a snapshot 183 takeSnapshot(tableName, Bytes.toString(snapshotName0), true); 184 185 // enable table and insert more data 186 admin.enableTable(tableName); 187 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 188 try (Table table = UTIL.getConnection().getTable(tableName)) { 189 snapshot1Rows = UTIL.countRows(table); 190 } 191 192 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 193 admin.disableTable(tableName); 194 takeSnapshot(tableName, Bytes.toString(snapshotName1), true); 195 196 // Restore from snapshot-0 197 admin.restoreSnapshot(snapshotName0); 198 admin.enableTable(tableName); 199 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows); 200 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 201 202 // Restore from emptySnapshot 203 admin.disableTable(tableName); 204 admin.restoreSnapshot(emptySnapshot); 205 admin.enableTable(tableName); 206 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0); 207 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 208 209 // Restore from snapshot-1 210 admin.disableTable(tableName); 211 admin.restoreSnapshot(snapshotName1); 212 admin.enableTable(tableName); 213 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 214 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 215 216 // Restore from snapshot-1 217 UTIL.deleteTable(tableName); 218 admin.restoreSnapshot(snapshotName1); 219 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 220 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 221 } 222 223 @Test 224 public void testRestoreEnabledSnapshot() 225 throws IOException, InterruptedException { 226 long tid = System.currentTimeMillis(); 227 TableName tableName = TableName.valueOf("testtb-" + tid); 228 byte[] emptySnapshot = Bytes.toBytes("emptySnaptb-" + tid); 229 byte[] snapshotName0 = Bytes.toBytes("snaptb0-" + tid); 230 byte[] snapshotName1 = Bytes.toBytes("snaptb1-" + tid); 231 int snapshot0Rows; 232 int snapshot1Rows; 233 234 // create Table 235 SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM); 236 237 // take an empty snapshot 238 takeSnapshot(tableName, Bytes.toString(emptySnapshot), false); 239 240 // Insert data 241 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 242 try (Table table = UTIL.getConnection().getTable(tableName)) { 243 snapshot0Rows = UTIL.countRows(table); 244 } 245 246 // take a snapshot 247 takeSnapshot(tableName, Bytes.toString(snapshotName0), false); 248 249 // Insert more data 250 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 251 try (Table table = UTIL.getConnection().getTable(tableName)) { 252 snapshot1Rows = UTIL.countRows(table); 253 } 254 255 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 256 takeSnapshot(tableName, Bytes.toString(snapshotName1), false); 257 258 // Restore from snapshot-0 259 admin.disableTable(tableName); 260 admin.restoreSnapshot(snapshotName0); 261 admin.enableTable(tableName); 262 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows); 263 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 264 265 // Restore from emptySnapshot 266 admin.disableTable(tableName); 267 admin.restoreSnapshot(emptySnapshot); 268 admin.enableTable(tableName); 269 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0); 270 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 271 272 // Restore from snapshot-1 273 admin.disableTable(tableName); 274 admin.restoreSnapshot(snapshotName1); 275 admin.enableTable(tableName); 276 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 277 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 278 279 // Restore from snapshot-1 280 UTIL.deleteTable(tableName); 281 admin.restoreSnapshot(snapshotName1); 282 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 283 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 284 } 285 286 /** 287 * Test snapshotting a table that is offline 288 * 289 * @throws Exception if snapshot does not complete successfully 290 */ 291 @Test 292 public void testOfflineTableSnapshot() throws Exception { 293 Admin admin = UTIL.getHBaseAdmin(); 294 // make sure we don't fail on listing snapshots 295 SnapshotTestingUtils.assertNoSnapshots(admin); 296 297 // put some stuff in the table 298 Table table = UTIL.getConnection().getTable(TABLE_NAME); 299 UTIL.loadTable(table, TEST_FAM, false); 300 301 LOG.debug("FS state before disable:"); 302 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 303 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 304 // XXX if this is flakey, might want to consider using the async version and looping as 305 // disableTable can succeed and still timeout. 306 admin.disableTable(TABLE_NAME); 307 308 LOG.debug("FS state before snapshot:"); 309 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 310 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 311 312 // take a snapshot of the disabled table 313 final String SNAPSHOT_NAME = "offlineTableSnapshot"; 314 byte[] snapshot = Bytes.toBytes(SNAPSHOT_NAME); 315 takeSnapshot(TABLE_NAME, SNAPSHOT_NAME, true); 316 LOG.debug("Snapshot completed."); 317 318 // make sure we have the snapshot 319 List<SnapshotDescription> snapshots = 320 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME); 321 322 // make sure its a valid snapshot 323 FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem(); 324 Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 325 LOG.debug("FS state after snapshot:"); 326 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 327 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 328 329 SnapshotTestingUtils 330 .confirmSnapshotValid(ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), 331 TABLE_NAME, TEST_FAM, rootDir, admin, fs); 332 333 admin.deleteSnapshot(snapshot); 334 SnapshotTestingUtils.assertNoSnapshots(admin); 335 } 336 337 /** 338 * Tests that snapshot has correct contents by taking snapshot, cloning it, then affirming 339 * the contents of the original and cloned table match 340 * 341 * @throws Exception if snapshot does not complete successfully 342 */ 343 @Test 344 public void testSnapshotCloneContents() throws Exception { 345 // make sure we don't fail on listing snapshots 346 SnapshotTestingUtils.assertNoSnapshots(admin); 347 348 // put some stuff in the table 349 Table table = UTIL.getConnection().getTable(TABLE_NAME); 350 UTIL.loadTable(table, TEST_FAM); 351 table.close(); 352 353 String snapshot1 = "TableSnapshot1"; 354 takeSnapshot(TABLE_NAME, snapshot1, false); 355 LOG.debug("Snapshot1 completed."); 356 357 TableName clone = TableName.valueOf("Table1Clone"); 358 admin.cloneSnapshot(snapshot1, clone, false); 359 360 Scan original = new Scan(); 361 Scan cloned = new Scan(); 362 ResultScanner originalScan = admin.getConnection().getTable(TABLE_NAME).getScanner(original); 363 ResultScanner clonedScan = 364 admin.getConnection().getTable(TableName.valueOf("Table1Clone")).getScanner(cloned); 365 366 Iterator<Result> i = originalScan.iterator(); 367 Iterator<Result> i2 = clonedScan.iterator(); 368 assertTrue(i.hasNext()); 369 while (i.hasNext()) { 370 assertTrue(i2.hasNext()); 371 assertEquals(Bytes.toString(i.next().getValue(TEST_FAM, new byte[] {})), 372 Bytes.toString(i2.next().getValue(TEST_FAM, new byte[] {}))); 373 } 374 assertFalse(i2.hasNext()); 375 admin.deleteSnapshot(snapshot1); 376 UTIL.deleteTable(clone); 377 admin.close(); 378 } 379 380 @Test 381 public void testOfflineTableSnapshotWithEmptyRegion() throws Exception { 382 // test with an empty table with one region 383 384 // make sure we don't fail on listing snapshots 385 SnapshotTestingUtils.assertNoSnapshots(admin); 386 387 LOG.debug("FS state before disable:"); 388 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 389 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 390 admin.disableTable(TABLE_NAME); 391 392 LOG.debug("FS state before snapshot:"); 393 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 394 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 395 396 // take a snapshot of the disabled table 397 byte[] snapshot = Bytes.toBytes("testOfflineTableSnapshotWithEmptyRegion"); 398 takeSnapshot(TABLE_NAME, Bytes.toString(snapshot), true); 399 LOG.debug("Snapshot completed."); 400 401 // make sure we have the snapshot 402 List<SnapshotDescription> snapshots = 403 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME); 404 405 // make sure its a valid snapshot 406 FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem(); 407 Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 408 LOG.debug("FS state after snapshot:"); 409 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 410 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 411 412 List<byte[]> emptyCfs = Lists.newArrayList(TEST_FAM); // no file in the region 413 List<byte[]> nonEmptyCfs = Lists.newArrayList(); 414 SnapshotTestingUtils 415 .confirmSnapshotValid(ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), 416 TABLE_NAME, nonEmptyCfs, emptyCfs, rootDir, admin, fs); 417 418 admin.deleteSnapshot(snapshot); 419 SnapshotTestingUtils.assertNoSnapshots(admin); 420 } 421 422 // Ensures that the snapshot is transferred to the proper completed snapshot directory 423 @Test 424 public void testEnsureTemporaryDirectoryTransfer() throws Exception { 425 Admin admin = null; 426 TableName tableName2 = TableName.valueOf("testListTableSnapshots"); 427 try { 428 admin = UTIL.getHBaseAdmin(); 429 430 HTableDescriptor htd = new HTableDescriptor(tableName2); 431 UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration()); 432 433 String table1Snapshot1 = "Table1Snapshot1"; 434 takeSnapshot(TABLE_NAME, table1Snapshot1, false); 435 LOG.debug("Snapshot1 completed."); 436 437 String table1Snapshot2 = "Table1Snapshot2"; 438 takeSnapshot(TABLE_NAME, table1Snapshot2, false); 439 LOG.debug("Snapshot2 completed."); 440 441 String table2Snapshot1 = "Table2Snapshot1"; 442 takeSnapshot(TABLE_NAME, table2Snapshot1, false); 443 LOG.debug("Table2Snapshot1 completed."); 444 445 List<SnapshotDescription> listTableSnapshots = admin.listTableSnapshots("test.*", ".*"); 446 List<String> listTableSnapshotNames = new ArrayList<String>(); 447 assertEquals(3, listTableSnapshots.size()); 448 for (SnapshotDescription s : listTableSnapshots) { 449 listTableSnapshotNames.add(s.getName()); 450 } 451 assertTrue(listTableSnapshotNames.contains(table1Snapshot1)); 452 assertTrue(listTableSnapshotNames.contains(table1Snapshot2)); 453 assertTrue(listTableSnapshotNames.contains(table2Snapshot1)); 454 } finally { 455 if (admin != null) { 456 try { 457 admin.deleteSnapshots("Table.*"); 458 } catch (SnapshotDoesNotExistException ignore) { 459 } 460 if (admin.tableExists(tableName2)) { 461 UTIL.deleteTable(tableName2); 462 } 463 admin.close(); 464 } 465 } 466 } 467 468 private void takeSnapshot(TableName tableName, String snapshotName, boolean disabled) 469 throws IOException { 470 SnapshotType type = disabled ? SnapshotType.DISABLED : SnapshotType.FLUSH; 471 SnapshotDescription desc = new SnapshotDescription(snapshotName, tableName, type, null, -1, 472 manifestVersion, null); 473 admin.snapshot(desc); 474 } 475}