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}