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}