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