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.junit.Assert.assertEquals; 021import static org.junit.Assert.fail; 022 023import java.io.IOException; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FileSystem; 031import org.apache.hadoop.fs.Path; 032import org.apache.hadoop.hbase.HBaseClassTestRule; 033import org.apache.hadoop.hbase.HBaseTestingUtility; 034import org.apache.hadoop.hbase.HConstants; 035import org.apache.hadoop.hbase.HRegionInfo; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.TableNotFoundException; 038import org.apache.hadoop.hbase.client.Admin; 039import org.apache.hadoop.hbase.client.SnapshotDescription; 040import org.apache.hadoop.hbase.client.SnapshotType; 041import org.apache.hadoop.hbase.client.Table; 042import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; 043import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; 044import org.apache.hadoop.hbase.testclassification.LargeTests; 045import org.apache.hadoop.hbase.testclassification.RegionServerTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 048import org.junit.After; 049import org.junit.AfterClass; 050import org.junit.Before; 051import org.junit.BeforeClass; 052import org.junit.ClassRule; 053import org.junit.Test; 054import org.junit.experimental.categories.Category; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 059import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos; 060 061/** 062 * Test creating/using/deleting snapshots from the client 063 * <p> 064 * This is an end-to-end test for the snapshot utility TODO This is essentially a clone of 065 * TestSnapshotFromClient. This is worth refactoring this because there will be a few more flavors 066 * of snapshots that need to run these tests. 067 */ 068@Category({ RegionServerTests.class, LargeTests.class }) 069public class TestFlushSnapshotFromClient { 070 071 @ClassRule 072 public static final HBaseClassTestRule CLASS_RULE = 073 HBaseClassTestRule.forClass(TestFlushSnapshotFromClient.class); 074 075 private static final Logger LOG = LoggerFactory.getLogger(TestFlushSnapshotFromClient.class); 076 077 protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 078 protected static final int NUM_RS = 2; 079 protected static final byte[] TEST_FAM = Bytes.toBytes("fam"); 080 protected static final TableName TABLE_NAME = TableName.valueOf("test"); 081 protected final int DEFAULT_NUM_ROWS = 100; 082 protected Admin admin = null; 083 084 @BeforeClass 085 public static void setupCluster() throws Exception { 086 setupConf(UTIL.getConfiguration()); 087 UTIL.startMiniCluster(NUM_RS); 088 } 089 090 protected static void setupConf(Configuration conf) { 091 // disable the ui 092 conf.setInt("hbase.regionsever.info.port", -1); 093 // change the flush size to a small amount, regulating number of store files 094 conf.setInt("hbase.hregion.memstore.flush.size", 25000); 095 // so make sure we get a compaction when doing a load, but keep around some 096 // files in the store 097 conf.setInt("hbase.hstore.compaction.min", 10); 098 conf.setInt("hbase.hstore.compactionThreshold", 10); 099 // block writes if we get to 12 store files 100 conf.setInt("hbase.hstore.blockingStoreFiles", 12); 101 // Enable snapshot 102 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 103 conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, 104 ConstantSizeRegionSplitPolicy.class.getName()); 105 } 106 107 @Before 108 public void setup() throws Exception { 109 createTable(); 110 this.admin = UTIL.getConnection().getAdmin(); 111 } 112 113 protected void createTable() throws Exception { 114 SnapshotTestingUtils.createTable(UTIL, TABLE_NAME, TEST_FAM); 115 } 116 117 @After 118 public void tearDown() throws Exception { 119 UTIL.deleteTable(TABLE_NAME); 120 SnapshotTestingUtils.deleteAllSnapshots(this.admin); 121 this.admin.close(); 122 SnapshotTestingUtils.deleteArchiveDirectory(UTIL); 123 } 124 125 @AfterClass 126 public static void cleanupTest() throws Exception { 127 try { 128 UTIL.shutdownMiniCluster(); 129 } catch (Exception e) { 130 LOG.warn("failure shutting down cluster", e); 131 } 132 } 133 134 /** 135 * Test simple flush snapshotting a table that is online 136 */ 137 @Test 138 public void testFlushTableSnapshot() throws Exception { 139 // make sure we don't fail on listing snapshots 140 SnapshotTestingUtils.assertNoSnapshots(admin); 141 142 // put some stuff in the table 143 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, DEFAULT_NUM_ROWS, TEST_FAM); 144 145 LOG.debug("FS state before snapshot:"); 146 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 147 148 // take a snapshot of the enabled table 149 String snapshotString = "offlineTableSnapshot"; 150 byte[] snapshot = Bytes.toBytes(snapshotString); 151 admin.snapshot(snapshotString, TABLE_NAME, SnapshotType.FLUSH); 152 LOG.debug("Snapshot completed."); 153 154 // make sure we have the snapshot 155 List<SnapshotDescription> snapshots = 156 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME); 157 158 // make sure its a valid snapshot 159 LOG.debug("FS state after snapshot:"); 160 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 161 162 SnapshotTestingUtils.confirmSnapshotValid(UTIL, 163 ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, TEST_FAM); 164 } 165 166 /** 167 * Test snapshotting a table that is online without flushing 168 */ 169 @Test 170 public void testSkipFlushTableSnapshot() throws Exception { 171 // make sure we don't fail on listing snapshots 172 SnapshotTestingUtils.assertNoSnapshots(admin); 173 174 // put some stuff in the table 175 Table table = UTIL.getConnection().getTable(TABLE_NAME); 176 UTIL.loadTable(table, TEST_FAM); 177 UTIL.flush(TABLE_NAME); 178 179 LOG.debug("FS state before snapshot:"); 180 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 181 182 // take a snapshot of the enabled table 183 String snapshotString = "skipFlushTableSnapshot"; 184 byte[] snapshot = Bytes.toBytes(snapshotString); 185 admin.snapshot(snapshotString, TABLE_NAME, SnapshotType.SKIPFLUSH); 186 LOG.debug("Snapshot completed."); 187 188 // make sure we have the snapshot 189 List<SnapshotDescription> snapshots = 190 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME); 191 192 // make sure its a valid snapshot 193 LOG.debug("FS state after snapshot:"); 194 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 195 196 SnapshotTestingUtils.confirmSnapshotValid(UTIL, 197 ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, TEST_FAM); 198 199 admin.deleteSnapshot(snapshot); 200 snapshots = admin.listSnapshots(); 201 SnapshotTestingUtils.assertNoSnapshots(admin); 202 } 203 204 /** 205 * Test simple flush snapshotting a table that is online 206 */ 207 @Test 208 public void testFlushTableSnapshotWithProcedure() throws Exception { 209 // make sure we don't fail on listing snapshots 210 SnapshotTestingUtils.assertNoSnapshots(admin); 211 212 // put some stuff in the table 213 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, DEFAULT_NUM_ROWS, TEST_FAM); 214 215 LOG.debug("FS state before snapshot:"); 216 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 217 218 // take a snapshot of the enabled table 219 String snapshotString = "offlineTableSnapshot"; 220 byte[] snapshot = Bytes.toBytes(snapshotString); 221 Map<String, String> props = new HashMap<>(); 222 props.put("table", TABLE_NAME.getNameAsString()); 223 admin.execProcedure(SnapshotManager.ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION, snapshotString, 224 props); 225 226 LOG.debug("Snapshot completed."); 227 228 // make sure we have the snapshot 229 List<SnapshotDescription> snapshots = 230 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME); 231 232 // make sure its a valid snapshot 233 LOG.debug("FS state after snapshot:"); 234 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 235 236 SnapshotTestingUtils.confirmSnapshotValid(UTIL, 237 ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, TEST_FAM); 238 } 239 240 @Test 241 public void testSnapshotFailsOnNonExistantTable() throws Exception { 242 // make sure we don't fail on listing snapshots 243 SnapshotTestingUtils.assertNoSnapshots(admin); 244 TableName tableName = TableName.valueOf("_not_a_table"); 245 246 // make sure the table doesn't exist 247 boolean fail = false; 248 do { 249 try { 250 admin.getTableDescriptor(tableName); 251 fail = true; 252 LOG.error("Table:" + tableName + " already exists, checking a new name"); 253 tableName = TableName.valueOf(tableName + "!"); 254 } catch (TableNotFoundException e) { 255 fail = false; 256 } 257 } while (fail); 258 259 // snapshot the non-existant table 260 try { 261 admin.snapshot("fail", tableName, SnapshotType.FLUSH); 262 fail("Snapshot succeeded even though there is not table."); 263 } catch (SnapshotCreationException e) { 264 LOG.info("Correctly failed to snapshot a non-existant table:" + e.getMessage()); 265 } 266 } 267 268 @Test 269 public void testAsyncFlushSnapshot() throws Exception { 270 SnapshotProtos.SnapshotDescription snapshot = SnapshotProtos.SnapshotDescription.newBuilder() 271 .setName("asyncSnapshot").setTable(TABLE_NAME.getNameAsString()) 272 .setType(SnapshotProtos.SnapshotDescription.Type.FLUSH).build(); 273 274 // take the snapshot async 275 admin.snapshotAsync(new SnapshotDescription("asyncSnapshot", TABLE_NAME, SnapshotType.FLUSH)) 276 .get(); 277 LOG.info(" === Async Snapshot Completed ==="); 278 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 279 280 // make sure we get the snapshot 281 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot); 282 } 283 284 @Test 285 public void testSnapshotStateAfterMerge() throws Exception { 286 int numRows = DEFAULT_NUM_ROWS; 287 // make sure we don't fail on listing snapshots 288 SnapshotTestingUtils.assertNoSnapshots(admin); 289 // load the table so we have some data 290 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, numRows, TEST_FAM); 291 292 // Take a snapshot 293 String snapshotBeforeMergeName = "snapshotBeforeMerge"; 294 admin.snapshot(snapshotBeforeMergeName, TABLE_NAME, SnapshotType.FLUSH); 295 296 // Clone the table 297 TableName cloneBeforeMergeName = TableName.valueOf("cloneBeforeMerge"); 298 admin.cloneSnapshot(snapshotBeforeMergeName, cloneBeforeMergeName); 299 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneBeforeMergeName); 300 301 // Merge two regions 302 List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME); 303 Collections.sort(regions, new Comparator<HRegionInfo>() { 304 @Override 305 public int compare(HRegionInfo r1, HRegionInfo r2) { 306 return Bytes.compareTo(r1.getStartKey(), r2.getStartKey()); 307 } 308 }); 309 310 int numRegions = admin.getTableRegions(TABLE_NAME).size(); 311 int numRegionsAfterMerge = numRegions - 2; 312 admin.mergeRegionsAsync(regions.get(1).getEncodedNameAsBytes(), 313 regions.get(2).getEncodedNameAsBytes(), true); 314 admin.mergeRegionsAsync(regions.get(4).getEncodedNameAsBytes(), 315 regions.get(5).getEncodedNameAsBytes(), true); 316 317 // Verify that there's one region less 318 waitRegionsAfterMerge(numRegionsAfterMerge); 319 assertEquals(numRegionsAfterMerge, admin.getTableRegions(TABLE_NAME).size()); 320 321 // Clone the table 322 TableName cloneAfterMergeName = TableName.valueOf("cloneAfterMerge"); 323 admin.cloneSnapshot(snapshotBeforeMergeName, cloneAfterMergeName); 324 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneAfterMergeName); 325 326 verifyRowCount(UTIL, TABLE_NAME, numRows); 327 verifyRowCount(UTIL, cloneBeforeMergeName, numRows); 328 verifyRowCount(UTIL, cloneAfterMergeName, numRows); 329 330 // test that we can delete the snapshot 331 UTIL.deleteTable(cloneAfterMergeName); 332 UTIL.deleteTable(cloneBeforeMergeName); 333 } 334 335 @Test 336 public void testTakeSnapshotAfterMerge() throws Exception { 337 int numRows = DEFAULT_NUM_ROWS; 338 // make sure we don't fail on listing snapshots 339 SnapshotTestingUtils.assertNoSnapshots(admin); 340 // load the table so we have some data 341 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, numRows, TEST_FAM); 342 343 // Merge two regions 344 List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME); 345 Collections.sort(regions, new Comparator<HRegionInfo>() { 346 @Override 347 public int compare(HRegionInfo r1, HRegionInfo r2) { 348 return Bytes.compareTo(r1.getStartKey(), r2.getStartKey()); 349 } 350 }); 351 352 int numRegions = admin.getTableRegions(TABLE_NAME).size(); 353 int numRegionsAfterMerge = numRegions - 2; 354 admin.mergeRegionsAsync(regions.get(1).getEncodedNameAsBytes(), 355 regions.get(2).getEncodedNameAsBytes(), true); 356 admin.mergeRegionsAsync(regions.get(4).getEncodedNameAsBytes(), 357 regions.get(5).getEncodedNameAsBytes(), true); 358 359 waitRegionsAfterMerge(numRegionsAfterMerge); 360 assertEquals(numRegionsAfterMerge, admin.getTableRegions(TABLE_NAME).size()); 361 362 // Take a snapshot 363 String snapshotName = "snapshotAfterMerge"; 364 SnapshotTestingUtils.snapshot(admin, snapshotName, TABLE_NAME, SnapshotType.FLUSH, 3); 365 366 // Clone the table 367 TableName cloneName = TableName.valueOf("cloneMerge"); 368 admin.cloneSnapshot(snapshotName, cloneName); 369 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneName); 370 371 verifyRowCount(UTIL, TABLE_NAME, numRows); 372 verifyRowCount(UTIL, cloneName, numRows); 373 374 // test that we can delete the snapshot 375 UTIL.deleteTable(cloneName); 376 } 377 378 /** 379 * Basic end-to-end test of simple-flush-based snapshots 380 */ 381 @Test 382 public void testFlushCreateListDestroy() throws Exception { 383 LOG.debug("------- Starting Snapshot test -------------"); 384 // make sure we don't fail on listing snapshots 385 SnapshotTestingUtils.assertNoSnapshots(admin); 386 // load the table so we have some data 387 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, DEFAULT_NUM_ROWS, TEST_FAM); 388 389 String snapshotName = "flushSnapshotCreateListDestroy"; 390 FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem(); 391 Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 392 SnapshotTestingUtils.createSnapshotAndValidate(admin, TABLE_NAME, Bytes.toString(TEST_FAM), 393 snapshotName, rootDir, fs, true); 394 } 395 396 private void waitRegionsAfterMerge(final long numRegionsAfterMerge) 397 throws IOException, InterruptedException { 398 // Verify that there's one region less 399 long startTime = EnvironmentEdgeManager.currentTime(); 400 while (admin.getTableRegions(TABLE_NAME).size() != numRegionsAfterMerge) { 401 // This may be flaky... if after 15sec the merge is not complete give up 402 // it will fail in the assertEquals(numRegionsAfterMerge). 403 if ((EnvironmentEdgeManager.currentTime() - startTime) > 15000) { 404 break; 405 } 406 Thread.sleep(100); 407 } 408 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, TABLE_NAME); 409 } 410 411 protected void verifyRowCount(final HBaseTestingUtility util, final TableName tableName, 412 long expectedRows) throws IOException { 413 SnapshotTestingUtils.verifyRowCount(util, tableName, expectedRows); 414 } 415 416 protected int countRows(final Table table, final byte[]... families) throws IOException { 417 return UTIL.countRows(table, families); 418 } 419}