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