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.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.apache.hadoop.hbase.util.EnvironmentEdgeManager; 052import org.junit.After; 053import org.junit.AfterClass; 054import org.junit.Before; 055import org.junit.BeforeClass; 056import org.junit.ClassRule; 057import org.junit.Test; 058import org.junit.experimental.categories.Category; 059import org.junit.runner.RunWith; 060import org.junit.runners.Parameterized; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063 064import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 065 066import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 067 068/** 069 * This class tests that the use of a temporary snapshot directory supports snapshot functionality 070 * while the temporary directory is on a different file system than the root directory 071 * <p> 072 * This is an end-to-end test for the snapshot utility 073 */ 074@Category(LargeTests.class) 075@RunWith(Parameterized.class) 076public class TestSnapshotTemporaryDirectory { 077 078 @ClassRule 079 public static final HBaseClassTestRule CLASS_RULE = 080 HBaseClassTestRule.forClass(TestSnapshotTemporaryDirectory.class); 081 082 @Parameterized.Parameters 083 public static Iterable<Integer> data() { 084 return Arrays.asList(SnapshotManifestV1.DESCRIPTOR_VERSION, 085 SnapshotManifestV2.DESCRIPTOR_VERSION); 086 } 087 088 @Parameterized.Parameter 089 public int manifestVersion; 090 091 private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotTemporaryDirectory.class); 092 protected static final int NUM_RS = 2; 093 protected static String TEMP_DIR = 094 Paths.get("").toAbsolutePath().toString() + Path.SEPARATOR + UUID.randomUUID().toString(); 095 096 protected static Admin admin; 097 protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 098 protected static final String STRING_TABLE_NAME = "test"; 099 protected static final byte[] TEST_FAM = Bytes.toBytes("fam"); 100 protected static final TableName TABLE_NAME = TableName.valueOf(STRING_TABLE_NAME); 101 102 /** 103 * Setup the config for the cluster 104 * @throws Exception on failure 105 */ 106 @BeforeClass 107 public static void setupCluster() throws Exception { 108 setupConf(UTIL.getConfiguration()); 109 UTIL.startMiniCluster(NUM_RS); 110 admin = UTIL.getHBaseAdmin(); 111 } 112 113 private static void setupConf(Configuration conf) { 114 // disable the ui 115 conf.setInt("hbase.regionsever.info.port", -1); 116 // change the flush size to a small amount, regulating number of store files 117 conf.setInt("hbase.hregion.memstore.flush.size", 25000); 118 // so make sure we get a compaction when doing a load, but keep around some 119 // files in the store 120 conf.setInt("hbase.hstore.compaction.min", 10); 121 conf.setInt("hbase.hstore.compactionThreshold", 10); 122 // block writes if we get to 12 store files 123 conf.setInt("hbase.hstore.blockingStoreFiles", 12); 124 // Enable snapshot 125 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 126 conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, 127 ConstantSizeRegionSplitPolicy.class.getName()); 128 conf.set(SnapshotDescriptionUtils.SNAPSHOT_WORKING_DIR, 129 "file://" + new Path(TEMP_DIR, ".tmpDir").toUri()); 130 } 131 132 @Before 133 public void setup() throws Exception { 134 HTableDescriptor htd = new HTableDescriptor(TABLE_NAME); 135 htd.setRegionReplication(getNumReplicas()); 136 UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration()); 137 } 138 139 protected int getNumReplicas() { 140 return 1; 141 } 142 143 @After 144 public void tearDown() throws Exception { 145 UTIL.deleteTable(TABLE_NAME); 146 SnapshotTestingUtils.deleteAllSnapshots(UTIL.getHBaseAdmin()); 147 SnapshotTestingUtils.deleteArchiveDirectory(UTIL); 148 } 149 150 @AfterClass 151 public static void cleanupTest() { 152 try { 153 UTIL.shutdownMiniCluster(); 154 FileUtils.deleteDirectory(new File(TEMP_DIR)); 155 } catch (Exception e) { 156 LOG.warn("failure shutting down cluster", e); 157 } 158 } 159 160 @Test 161 public void testRestoreDisabledSnapshot() throws IOException, InterruptedException { 162 long tid = EnvironmentEdgeManager.currentTime(); 163 TableName tableName = TableName.valueOf("testtb-" + tid); 164 byte[] emptySnapshot = Bytes.toBytes("emptySnaptb-" + tid); 165 byte[] snapshotName0 = Bytes.toBytes("snaptb0-" + tid); 166 byte[] snapshotName1 = Bytes.toBytes("snaptb1-" + tid); 167 int snapshot0Rows; 168 int snapshot1Rows; 169 170 // create Table and disable it 171 SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM); 172 admin.disableTable(tableName); 173 174 // take an empty snapshot 175 takeSnapshot(tableName, Bytes.toString(emptySnapshot), true); 176 177 // enable table and insert data 178 admin.enableTable(tableName); 179 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 180 try (Table table = UTIL.getConnection().getTable(tableName)) { 181 snapshot0Rows = UTIL.countRows(table); 182 } 183 admin.disableTable(tableName); 184 185 // take a snapshot 186 takeSnapshot(tableName, Bytes.toString(snapshotName0), true); 187 188 // enable table and insert more data 189 admin.enableTable(tableName); 190 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 191 try (Table table = UTIL.getConnection().getTable(tableName)) { 192 snapshot1Rows = UTIL.countRows(table); 193 } 194 195 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 196 admin.disableTable(tableName); 197 takeSnapshot(tableName, Bytes.toString(snapshotName1), true); 198 199 // Restore from snapshot-0 200 admin.restoreSnapshot(snapshotName0); 201 admin.enableTable(tableName); 202 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows); 203 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 204 205 // Restore from emptySnapshot 206 admin.disableTable(tableName); 207 admin.restoreSnapshot(emptySnapshot); 208 admin.enableTable(tableName); 209 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0); 210 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 211 212 // Restore from snapshot-1 213 admin.disableTable(tableName); 214 admin.restoreSnapshot(snapshotName1); 215 admin.enableTable(tableName); 216 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 217 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 218 219 // Restore from snapshot-1 220 UTIL.deleteTable(tableName); 221 admin.restoreSnapshot(snapshotName1); 222 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 223 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 224 } 225 226 @Test 227 public void testRestoreEnabledSnapshot() throws IOException, InterruptedException { 228 long tid = EnvironmentEdgeManager.currentTime(); 229 TableName tableName = TableName.valueOf("testtb-" + tid); 230 byte[] emptySnapshot = Bytes.toBytes("emptySnaptb-" + tid); 231 byte[] snapshotName0 = Bytes.toBytes("snaptb0-" + tid); 232 byte[] snapshotName1 = Bytes.toBytes("snaptb1-" + tid); 233 int snapshot0Rows; 234 int snapshot1Rows; 235 236 // create Table 237 SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM); 238 239 // take an empty snapshot 240 takeSnapshot(tableName, Bytes.toString(emptySnapshot), false); 241 242 // Insert data 243 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 244 try (Table table = UTIL.getConnection().getTable(tableName)) { 245 snapshot0Rows = UTIL.countRows(table); 246 } 247 248 // take a snapshot 249 takeSnapshot(tableName, Bytes.toString(snapshotName0), false); 250 251 // Insert more data 252 SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM); 253 try (Table table = UTIL.getConnection().getTable(tableName)) { 254 snapshot1Rows = UTIL.countRows(table); 255 } 256 257 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 258 takeSnapshot(tableName, Bytes.toString(snapshotName1), false); 259 260 // Restore from snapshot-0 261 admin.disableTable(tableName); 262 admin.restoreSnapshot(snapshotName0); 263 admin.enableTable(tableName); 264 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows); 265 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 266 267 // Restore from emptySnapshot 268 admin.disableTable(tableName); 269 admin.restoreSnapshot(emptySnapshot); 270 admin.enableTable(tableName); 271 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0); 272 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 273 274 // Restore from snapshot-1 275 admin.disableTable(tableName); 276 admin.restoreSnapshot(snapshotName1); 277 admin.enableTable(tableName); 278 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 279 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 280 281 // Restore from snapshot-1 282 UTIL.deleteTable(tableName); 283 admin.restoreSnapshot(snapshotName1); 284 SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows); 285 SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas()); 286 } 287 288 /** 289 * Test snapshotting a table that is offline 290 * @throws Exception if snapshot does not complete successfully 291 */ 292 @Test 293 public void testOfflineTableSnapshot() throws Exception { 294 Admin admin = UTIL.getHBaseAdmin(); 295 // make sure we don't fail on listing snapshots 296 SnapshotTestingUtils.assertNoSnapshots(admin); 297 298 // put some stuff in the table 299 Table table = UTIL.getConnection().getTable(TABLE_NAME); 300 UTIL.loadTable(table, TEST_FAM, false); 301 302 LOG.debug("FS state before disable:"); 303 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 304 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 305 // XXX if this is flakey, might want to consider using the async version and looping as 306 // disableTable can succeed and still timeout. 307 admin.disableTable(TABLE_NAME); 308 309 LOG.debug("FS state before snapshot:"); 310 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 311 CommonFSUtils.getRootDir(UTIL.getConfiguration()), 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 CommonFSUtils.logFileSystemState(UTIL.getTestFileSystem(), 328 CommonFSUtils.getRootDir(UTIL.getConfiguration()), LOG); 329 330 SnapshotTestingUtils.confirmSnapshotValid( 331 ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, TEST_FAM, rootDir, 332 admin, fs); 333 334 admin.deleteSnapshot(snapshot); 335 SnapshotTestingUtils.assertNoSnapshots(admin); 336 } 337 338 /** 339 * Tests that snapshot has correct contents by taking snapshot, cloning it, then affirming the 340 * contents of the original and cloned table match 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.confirmSnapshotValid( 415 ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, nonEmptyCfs, 416 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 = 472 new SnapshotDescription(snapshotName, tableName, type, null, -1, manifestVersion, null); 473 admin.snapshot(desc); 474 } 475}