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