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.apache.hadoop.hbase.util.EnvironmentEdgeManager; 049import org.junit.After; 050import org.junit.AfterClass; 051import org.junit.Before; 052import org.junit.BeforeClass; 053import org.junit.ClassRule; 054import org.junit.Test; 055import org.junit.experimental.categories.Category; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058 059import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 060import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos; 061 062/** 063 * Test creating/using/deleting snapshots from the client 064 * <p> 065 * This is an end-to-end test for the snapshot utility TODO This is essentially a clone of 066 * TestSnapshotFromClient. This is worth refactoring this because there will be a few more flavors 067 * 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 * Test simple flush snapshotting a table that is online 207 */ 208 @Test 209 public void testFlushTableSnapshotWithProcedure() throws Exception { 210 // make sure we don't fail on listing snapshots 211 SnapshotTestingUtils.assertNoSnapshots(admin); 212 213 // put some stuff in the table 214 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, DEFAULT_NUM_ROWS, TEST_FAM); 215 216 LOG.debug("FS state before snapshot:"); 217 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 218 219 // take a snapshot of the enabled table 220 String snapshotString = "offlineTableSnapshot"; 221 byte[] snapshot = Bytes.toBytes(snapshotString); 222 Map<String, String> props = new HashMap<>(); 223 props.put("table", TABLE_NAME.getNameAsString()); 224 admin.execProcedure(SnapshotManager.ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION, snapshotString, 225 props); 226 227 LOG.debug("Snapshot completed."); 228 229 // make sure we have the snapshot 230 List<SnapshotDescription> snapshots = 231 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME); 232 233 // make sure its a valid snapshot 234 LOG.debug("FS state after snapshot:"); 235 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 236 237 SnapshotTestingUtils.confirmSnapshotValid(UTIL, 238 ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), TABLE_NAME, TEST_FAM); 239 } 240 241 @Test 242 public void testSnapshotFailsOnNonExistantTable() throws Exception { 243 // make sure we don't fail on listing snapshots 244 SnapshotTestingUtils.assertNoSnapshots(admin); 245 TableName tableName = TableName.valueOf("_not_a_table"); 246 247 // make sure the table doesn't exist 248 boolean fail = false; 249 do { 250 try { 251 admin.getTableDescriptor(tableName); 252 fail = true; 253 LOG.error("Table:" + tableName + " already exists, checking a new name"); 254 tableName = TableName.valueOf(tableName + "!"); 255 } catch (TableNotFoundException e) { 256 fail = false; 257 } 258 } while (fail); 259 260 // snapshot the non-existant table 261 try { 262 admin.snapshot("fail", tableName, SnapshotType.FLUSH); 263 fail("Snapshot succeeded even though there is not table."); 264 } catch (SnapshotCreationException e) { 265 LOG.info("Correctly failed to snapshot a non-existant table:" + e.getMessage()); 266 } 267 } 268 269 @Test 270 public void testAsyncFlushSnapshot() throws Exception { 271 SnapshotProtos.SnapshotDescription snapshot = SnapshotProtos.SnapshotDescription.newBuilder() 272 .setName("asyncSnapshot").setTable(TABLE_NAME.getNameAsString()) 273 .setType(SnapshotProtos.SnapshotDescription.Type.FLUSH).build(); 274 275 // take the snapshot async 276 admin 277 .takeSnapshotAsync(new SnapshotDescription("asyncSnapshot", TABLE_NAME, SnapshotType.FLUSH)); 278 279 // constantly loop, looking for the snapshot to complete 280 HMaster master = UTIL.getMiniHBaseCluster().getMaster(); 281 SnapshotTestingUtils.waitForSnapshotToComplete(master, snapshot, 200); 282 LOG.info(" === Async Snapshot Completed ==="); 283 UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG); 284 285 // make sure we get the snapshot 286 SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot); 287 } 288 289 @Test 290 public void testSnapshotStateAfterMerge() throws Exception { 291 int numRows = DEFAULT_NUM_ROWS; 292 // make sure we don't fail on listing snapshots 293 SnapshotTestingUtils.assertNoSnapshots(admin); 294 // load the table so we have some data 295 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, numRows, TEST_FAM); 296 297 // Take a snapshot 298 String snapshotBeforeMergeName = "snapshotBeforeMerge"; 299 admin.snapshot(snapshotBeforeMergeName, TABLE_NAME, SnapshotType.FLUSH); 300 301 // Clone the table 302 TableName cloneBeforeMergeName = TableName.valueOf("cloneBeforeMerge"); 303 admin.cloneSnapshot(snapshotBeforeMergeName, cloneBeforeMergeName); 304 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneBeforeMergeName); 305 306 // Merge two regions 307 List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME); 308 Collections.sort(regions, new Comparator<HRegionInfo>() { 309 @Override 310 public int compare(HRegionInfo r1, HRegionInfo r2) { 311 return Bytes.compareTo(r1.getStartKey(), r2.getStartKey()); 312 } 313 }); 314 315 int numRegions = admin.getTableRegions(TABLE_NAME).size(); 316 int numRegionsAfterMerge = numRegions - 2; 317 admin.mergeRegionsAsync(regions.get(1).getEncodedNameAsBytes(), 318 regions.get(2).getEncodedNameAsBytes(), true); 319 admin.mergeRegionsAsync(regions.get(4).getEncodedNameAsBytes(), 320 regions.get(5).getEncodedNameAsBytes(), true); 321 322 // Verify that there's one region less 323 waitRegionsAfterMerge(numRegionsAfterMerge); 324 assertEquals(numRegionsAfterMerge, admin.getTableRegions(TABLE_NAME).size()); 325 326 // Clone the table 327 TableName cloneAfterMergeName = TableName.valueOf("cloneAfterMerge"); 328 admin.cloneSnapshot(snapshotBeforeMergeName, cloneAfterMergeName); 329 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneAfterMergeName); 330 331 verifyRowCount(UTIL, TABLE_NAME, numRows); 332 verifyRowCount(UTIL, cloneBeforeMergeName, numRows); 333 verifyRowCount(UTIL, cloneAfterMergeName, numRows); 334 335 // test that we can delete the snapshot 336 UTIL.deleteTable(cloneAfterMergeName); 337 UTIL.deleteTable(cloneBeforeMergeName); 338 } 339 340 @Test 341 public void testTakeSnapshotAfterMerge() throws Exception { 342 int numRows = DEFAULT_NUM_ROWS; 343 // make sure we don't fail on listing snapshots 344 SnapshotTestingUtils.assertNoSnapshots(admin); 345 // load the table so we have some data 346 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, numRows, TEST_FAM); 347 348 // Merge two regions 349 List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME); 350 Collections.sort(regions, new Comparator<HRegionInfo>() { 351 @Override 352 public int compare(HRegionInfo r1, HRegionInfo r2) { 353 return Bytes.compareTo(r1.getStartKey(), r2.getStartKey()); 354 } 355 }); 356 357 int numRegions = admin.getTableRegions(TABLE_NAME).size(); 358 int numRegionsAfterMerge = numRegions - 2; 359 admin.mergeRegionsAsync(regions.get(1).getEncodedNameAsBytes(), 360 regions.get(2).getEncodedNameAsBytes(), true); 361 admin.mergeRegionsAsync(regions.get(4).getEncodedNameAsBytes(), 362 regions.get(5).getEncodedNameAsBytes(), true); 363 364 waitRegionsAfterMerge(numRegionsAfterMerge); 365 assertEquals(numRegionsAfterMerge, admin.getTableRegions(TABLE_NAME).size()); 366 367 // Take a snapshot 368 String snapshotName = "snapshotAfterMerge"; 369 SnapshotTestingUtils.snapshot(admin, snapshotName, TABLE_NAME, SnapshotType.FLUSH, 3); 370 371 // Clone the table 372 TableName cloneName = TableName.valueOf("cloneMerge"); 373 admin.cloneSnapshot(snapshotName, cloneName); 374 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneName); 375 376 verifyRowCount(UTIL, TABLE_NAME, numRows); 377 verifyRowCount(UTIL, cloneName, numRows); 378 379 // test that we can delete the snapshot 380 UTIL.deleteTable(cloneName); 381 } 382 383 /** 384 * Basic end-to-end test of simple-flush-based snapshots 385 */ 386 @Test 387 public void testFlushCreateListDestroy() throws Exception { 388 LOG.debug("------- Starting Snapshot test -------------"); 389 // make sure we don't fail on listing snapshots 390 SnapshotTestingUtils.assertNoSnapshots(admin); 391 // load the table so we have some data 392 SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, DEFAULT_NUM_ROWS, TEST_FAM); 393 394 String snapshotName = "flushSnapshotCreateListDestroy"; 395 FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem(); 396 Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 397 SnapshotTestingUtils.createSnapshotAndValidate(admin, TABLE_NAME, Bytes.toString(TEST_FAM), 398 snapshotName, rootDir, fs, true); 399 } 400 401 private void waitRegionsAfterMerge(final long numRegionsAfterMerge) 402 throws IOException, InterruptedException { 403 // Verify that there's one region less 404 long startTime = EnvironmentEdgeManager.currentTime(); 405 while (admin.getTableRegions(TABLE_NAME).size() != numRegionsAfterMerge) { 406 // This may be flaky... if after 15sec the merge is not complete give up 407 // it will fail in the assertEquals(numRegionsAfterMerge). 408 if ((EnvironmentEdgeManager.currentTime() - startTime) > 15000) { 409 break; 410 } 411 Thread.sleep(100); 412 } 413 SnapshotTestingUtils.waitForTableToBeOnline(UTIL, TABLE_NAME); 414 } 415 416 protected void verifyRowCount(final HBaseTestingUtility util, final TableName tableName, 417 long expectedRows) throws IOException { 418 SnapshotTestingUtils.verifyRowCount(util, tableName, expectedRows); 419 } 420 421 protected int countRows(final Table table, final byte[]... families) throws IOException { 422 return UTIL.countRows(table, families); 423 } 424}