View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.snapshot;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.fail;
22  
23  import java.io.IOException;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.fs.FileSystem;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.TableName;
39  import org.apache.hadoop.hbase.TableNotFoundException;
40  import org.apache.hadoop.hbase.client.Admin;
41  import org.apache.hadoop.hbase.client.Table;
42  import org.apache.hadoop.hbase.master.HMaster;
43  import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
44  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
45  import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
46  import org.apache.hadoop.hbase.testclassification.LargeTests;
47  import org.apache.hadoop.hbase.util.Bytes;
48  import org.junit.After;
49  import org.junit.AfterClass;
50  import org.junit.Before;
51  import org.junit.BeforeClass;
52  import org.junit.Test;
53  import org.junit.experimental.categories.Category;
54  
55  /**
56   * Test creating/using/deleting snapshots from the client
57   * <p>
58   * This is an end-to-end test for the snapshot utility
59   *
60   * TODO This is essentially a clone of TestSnapshotFromClient.  This is worth refactoring this
61   * because there will be a few more flavors of snapshots that need to run these tests.
62   */
63  @Category(LargeTests.class)
64  public class TestFlushSnapshotFromClient {
65    private static final Log LOG = LogFactory.getLog(TestFlushSnapshotFromClient.class);
66    protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
67    private static final int NUM_RS = 2;
68    private static final byte[] TEST_FAM = Bytes.toBytes("fam");
69    private static final TableName TABLE_NAME = TableName.valueOf("test");
70    private final int DEFAULT_NUM_ROWS = 100;
71  
72    /**
73     * Setup the config for the cluster
74     * @throws Exception on failure
75     */
76    @BeforeClass
77    public static void setupCluster() throws Exception {
78      // Uncomment the following lines if more verbosity is needed for
79      // debugging (see HBASE-12285 for details).
80      //((Log4JLogger)RpcServer.LOG).getLogger().setLevel(Level.ALL);
81      //((Log4JLogger)AbstractRpcClient.LOG).getLogger().setLevel(Level.ALL);
82      //((Log4JLogger)ScannerCallable.LOG).getLogger().setLevel(Level.ALL);
83      setupConf(UTIL.getConfiguration());
84      UTIL.startMiniCluster(NUM_RS);
85    }
86  
87    protected static void setupConf(Configuration conf) {
88      // disable the ui
89      conf.setInt("hbase.regionsever.info.port", -1);
90      // change the flush size to a small amount, regulating number of store files
91      conf.setInt("hbase.hregion.memstore.flush.size", 25000);
92      // so make sure we get a compaction when doing a load, but keep around some
93      // files in the store
94      conf.setInt("hbase.hstore.compaction.min", 10);
95      conf.setInt("hbase.hstore.compactionThreshold", 10);
96      // block writes if we get to 12 store files
97      conf.setInt("hbase.hstore.blockingStoreFiles", 12);
98      // Enable snapshot
99      conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
100     conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
101       ConstantSizeRegionSplitPolicy.class.getName());
102   }
103 
104   @Before
105   public void setup() throws Exception {
106     SnapshotTestingUtils.createTable(UTIL, TABLE_NAME, TEST_FAM);
107   }
108 
109   @After
110   public void tearDown() throws Exception {
111     UTIL.deleteTable(TABLE_NAME);
112 
113     SnapshotTestingUtils.deleteAllSnapshots(UTIL.getHBaseAdmin());
114     SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
115   }
116 
117   @AfterClass
118   public static void cleanupTest() throws Exception {
119     try {
120       UTIL.shutdownMiniCluster();
121     } catch (Exception e) {
122       LOG.warn("failure shutting down cluster", e);
123     }
124   }
125 
126   /**
127    * Test simple flush snapshotting a table that is online
128    * @throws Exception
129    */
130   @Test (timeout=300000)
131   public void testFlushTableSnapshot() throws Exception {
132     Admin admin = UTIL.getHBaseAdmin();
133     // make sure we don't fail on listing snapshots
134     SnapshotTestingUtils.assertNoSnapshots(admin);
135 
136     // put some stuff in the table
137     SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, DEFAULT_NUM_ROWS, TEST_FAM);
138 
139     LOG.debug("FS state before snapshot:");
140     UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG);
141 
142     // take a snapshot of the enabled table
143     String snapshotString = "offlineTableSnapshot";
144     byte[] snapshot = Bytes.toBytes(snapshotString);
145     admin.snapshot(snapshotString, TABLE_NAME, SnapshotDescription.Type.FLUSH);
146     LOG.debug("Snapshot completed.");
147 
148     // make sure we have the snapshot
149     List<SnapshotDescription> snapshots = SnapshotTestingUtils.assertOneSnapshotThatMatches(admin,
150       snapshot, TABLE_NAME);
151 
152     // make sure its a valid snapshot
153     LOG.debug("FS state after snapshot:");
154     UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG);
155 
156     SnapshotTestingUtils.confirmSnapshotValid(UTIL, snapshots.get(0), TABLE_NAME, TEST_FAM);
157   }
158 
159    /**
160    * Test snapshotting a table that is online without flushing
161    * @throws Exception
162    */
163   @Test(timeout=30000)
164   public void testSkipFlushTableSnapshot() throws Exception {
165     Admin admin = UTIL.getHBaseAdmin();
166     // make sure we don't fail on listing snapshots
167     SnapshotTestingUtils.assertNoSnapshots(admin);
168 
169     // put some stuff in the table
170     try (Table table = UTIL.getConnection().getTable(TABLE_NAME)) {
171       UTIL.loadTable(table, TEST_FAM);
172     }
173 
174     LOG.debug("FS state before snapshot:");
175     UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG);
176 
177     // take a snapshot of the enabled table
178     String snapshotString = "skipFlushTableSnapshot";
179     byte[] snapshot = Bytes.toBytes(snapshotString);
180     admin.snapshot(snapshotString, TABLE_NAME, SnapshotDescription.Type.SKIPFLUSH);
181     LOG.debug("Snapshot completed.");
182 
183     // make sure we have the snapshot
184     List<SnapshotDescription> snapshots = SnapshotTestingUtils.assertOneSnapshotThatMatches(admin,
185         snapshot, TABLE_NAME);
186 
187     // make sure its a valid snapshot
188     LOG.debug("FS state after snapshot:");
189     UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG);
190 
191     SnapshotTestingUtils.confirmSnapshotValid(UTIL, snapshots.get(0), TABLE_NAME, TEST_FAM);
192 
193     admin.deleteSnapshot(snapshot);
194     snapshots = admin.listSnapshots();
195     SnapshotTestingUtils.assertNoSnapshots(admin);
196   }
197 
198 
199   /**
200    * Test simple flush snapshotting a table that is online
201    * @throws Exception
202    */
203   @Test (timeout=300000)
204   public void testFlushTableSnapshotWithProcedure() throws Exception {
205     Admin admin = UTIL.getHBaseAdmin();
206     // make sure we don't fail on listing snapshots
207     SnapshotTestingUtils.assertNoSnapshots(admin);
208 
209     // put some stuff in the table
210     SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, DEFAULT_NUM_ROWS, TEST_FAM);
211 
212     LOG.debug("FS state before snapshot:");
213     UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG);
214 
215     // take a snapshot of the enabled table
216     String snapshotString = "offlineTableSnapshot";
217     byte[] snapshot = Bytes.toBytes(snapshotString);
218     Map<String, String> props = new HashMap<String, String>();
219     props.put("table", TABLE_NAME.getNameAsString());
220     admin.execProcedure(SnapshotManager.ONLINE_SNAPSHOT_CONTROLLER_DESCRIPTION,
221         snapshotString, props);
222 
223 
224     LOG.debug("Snapshot completed.");
225 
226     // make sure we have the snapshot
227     List<SnapshotDescription> snapshots = SnapshotTestingUtils.assertOneSnapshotThatMatches(admin,
228       snapshot, TABLE_NAME);
229 
230     // make sure its a valid snapshot
231     LOG.debug("FS state after snapshot:");
232     UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG);
233 
234     SnapshotTestingUtils.confirmSnapshotValid(UTIL, snapshots.get(0), TABLE_NAME, TEST_FAM);
235   }
236 
237   @Test (timeout=300000)
238   public void testSnapshotFailsOnNonExistantTable() throws Exception {
239     Admin admin = UTIL.getHBaseAdmin();
240     // make sure we don't fail on listing snapshots
241     SnapshotTestingUtils.assertNoSnapshots(admin);
242     TableName tableName = TableName.valueOf("_not_a_table");
243 
244     // make sure the table doesn't exist
245     boolean fail = false;
246     do {
247     try {
248       admin.getTableDescriptor(tableName);
249       fail = true;
250       LOG.error("Table:" + tableName + " already exists, checking a new name");
251       tableName = TableName.valueOf(tableName+"!");
252     } catch (TableNotFoundException e) {
253       fail = false;
254       }
255     } while (fail);
256 
257     // snapshot the non-existant table
258     try {
259       admin.snapshot("fail", tableName, SnapshotDescription.Type.FLUSH);
260       fail("Snapshot succeeded even though there is not table.");
261     } catch (SnapshotCreationException e) {
262       LOG.info("Correctly failed to snapshot a non-existant table:" + e.getMessage());
263     }
264   }
265 
266   @Test(timeout = 300000)
267   public void testAsyncFlushSnapshot() throws Exception {
268     Admin admin = UTIL.getHBaseAdmin();
269     SnapshotDescription snapshot = SnapshotDescription.newBuilder().setName("asyncSnapshot")
270         .setTable(TABLE_NAME.getNameAsString())
271         .setType(SnapshotDescription.Type.FLUSH)
272         .build();
273 
274     // take the snapshot async
275     admin.takeSnapshotAsync(snapshot);
276 
277     // constantly loop, looking for the snapshot to complete
278     HMaster master = UTIL.getMiniHBaseCluster().getMaster();
279     SnapshotTestingUtils.waitForSnapshotToComplete(master, snapshot, 200);
280     LOG.info(" === Async Snapshot Completed ===");
281     UTIL.getHBaseCluster().getMaster().getMasterFileSystem().logFileSystemState(LOG);
282 
283     // make sure we get the snapshot
284     SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot);
285   }
286 
287   @Test (timeout=300000)
288   public void testSnapshotStateAfterMerge() throws Exception {
289     int numRows = DEFAULT_NUM_ROWS;
290     Admin admin = UTIL.getHBaseAdmin();
291     // make sure we don't fail on listing snapshots
292     SnapshotTestingUtils.assertNoSnapshots(admin);
293     // load the table so we have some data
294     SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, numRows, TEST_FAM);
295 
296     // Take a snapshot
297     String snapshotBeforeMergeName = "snapshotBeforeMerge";
298     admin.snapshot(snapshotBeforeMergeName, TABLE_NAME, SnapshotDescription.Type.FLUSH);
299 
300     // Clone the table
301     TableName cloneBeforeMergeName = TableName.valueOf("cloneBeforeMerge");
302     admin.cloneSnapshot(snapshotBeforeMergeName, cloneBeforeMergeName);
303     SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneBeforeMergeName);
304 
305     // Merge two regions
306     List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME);
307     Collections.sort(regions, new Comparator<HRegionInfo>() {
308       public int compare(HRegionInfo r1, HRegionInfo r2) {
309         return Bytes.compareTo(r1.getStartKey(), r2.getStartKey());
310       }
311     });
312 
313     int numRegions = admin.getTableRegions(TABLE_NAME).size();
314     int numRegionsAfterMerge = numRegions - 2;
315     admin.mergeRegions(regions.get(1).getEncodedNameAsBytes(),
316         regions.get(2).getEncodedNameAsBytes(), true);
317     admin.mergeRegions(regions.get(5).getEncodedNameAsBytes(),
318         regions.get(6).getEncodedNameAsBytes(), true);
319 
320     // Verify that there's one region less
321     waitRegionsAfterMerge(numRegionsAfterMerge);
322     assertEquals(numRegionsAfterMerge, admin.getTableRegions(TABLE_NAME).size());
323 
324     // Clone the table
325     TableName cloneAfterMergeName = TableName.valueOf("cloneAfterMerge");
326     admin.cloneSnapshot(snapshotBeforeMergeName, cloneAfterMergeName);
327     SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneAfterMergeName);
328 
329     SnapshotTestingUtils.verifyRowCount(UTIL, TABLE_NAME, numRows);
330     SnapshotTestingUtils.verifyRowCount(UTIL, cloneBeforeMergeName, numRows);
331     SnapshotTestingUtils.verifyRowCount(UTIL, cloneAfterMergeName, numRows);
332 
333     // test that we can delete the snapshot
334     UTIL.deleteTable(cloneAfterMergeName);
335     UTIL.deleteTable(cloneBeforeMergeName);
336   }
337 
338   @Test (timeout=300000)
339   public void testTakeSnapshotAfterMerge() throws Exception {
340     int numRows = DEFAULT_NUM_ROWS;
341     Admin admin = UTIL.getHBaseAdmin();
342     // make sure we don't fail on listing snapshots
343     SnapshotTestingUtils.assertNoSnapshots(admin);
344     // load the table so we have some data
345     SnapshotTestingUtils.loadData(UTIL, TABLE_NAME, numRows, TEST_FAM);
346 
347     // Merge two regions
348     List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME);
349     Collections.sort(regions, new Comparator<HRegionInfo>() {
350       public int compare(HRegionInfo r1, HRegionInfo r2) {
351         return Bytes.compareTo(r1.getStartKey(), r2.getStartKey());
352       }
353     });
354 
355     int numRegions = admin.getTableRegions(TABLE_NAME).size();
356     int numRegionsAfterMerge = numRegions - 2;
357     admin.mergeRegions(regions.get(1).getEncodedNameAsBytes(),
358         regions.get(2).getEncodedNameAsBytes(), true);
359     admin.mergeRegions(regions.get(5).getEncodedNameAsBytes(),
360         regions.get(6).getEncodedNameAsBytes(), true);
361 
362     waitRegionsAfterMerge(numRegionsAfterMerge);
363     assertEquals(numRegionsAfterMerge, admin.getTableRegions(TABLE_NAME).size());
364 
365     // Take a snapshot
366     String snapshotName = "snapshotAfterMerge";
367     SnapshotTestingUtils.snapshot(admin, snapshotName, TABLE_NAME.getNameAsString(),
368       SnapshotDescription.Type.FLUSH, 3);
369 
370     // Clone the table
371     TableName cloneName = TableName.valueOf("cloneMerge");
372     admin.cloneSnapshot(snapshotName, cloneName);
373     SnapshotTestingUtils.waitForTableToBeOnline(UTIL, cloneName);
374 
375     SnapshotTestingUtils.verifyRowCount(UTIL, TABLE_NAME, numRows);
376     SnapshotTestingUtils.verifyRowCount(UTIL, cloneName, numRows);
377 
378     // test that we can delete the snapshot
379     UTIL.deleteTable(cloneName);
380   }
381 
382   /**
383    * Basic end-to-end test of simple-flush-based snapshots
384    */
385   @Test (timeout=300000)
386   public void testFlushCreateListDestroy() throws Exception {
387     LOG.debug("------- Starting Snapshot test -------------");
388     Admin admin = UTIL.getHBaseAdmin();
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     Admin admin = UTIL.getHBaseAdmin();
404     // Verify that there's one region less
405     long startTime = System.currentTimeMillis();
406     while (admin.getTableRegions(TABLE_NAME).size() != numRegionsAfterMerge) {
407       // This may be flaky... if after 15sec the merge is not complete give up
408       // it will fail in the assertEquals(numRegionsAfterMerge).
409       if ((System.currentTimeMillis() - startTime) > 15000)
410         break;
411       Thread.sleep(100);
412     }
413     SnapshotTestingUtils.waitForTableToBeOnline(UTIL, TABLE_NAME);
414   }
415 }