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}