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  
19  package org.apache.hadoop.hbase.client;
20  
21  import java.util.List;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.fs.FileSystem;
27  import org.apache.hadoop.fs.Path;
28  import org.apache.hadoop.hbase.Cell;
29  import org.apache.hadoop.hbase.CellUtil;
30  import org.apache.hadoop.hbase.TableName;
31  import org.apache.hadoop.hbase.HBaseTestingUtility;
32  import org.apache.hadoop.hbase.HColumnDescriptor;
33  import org.apache.hadoop.hbase.HConstants;
34  import org.apache.hadoop.hbase.HRegionInfo;
35  import org.apache.hadoop.hbase.HTableDescriptor;
36  import org.apache.hadoop.hbase.master.cleaner.HFileCleaner;
37  import org.apache.hadoop.hbase.master.cleaner.HFileLinkCleaner;
38  import org.apache.hadoop.hbase.master.snapshot.SnapshotHFileCleaner;
39  import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
40  import org.apache.hadoop.hbase.mob.MobConstants;
41  import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
42  import org.apache.hadoop.hbase.snapshot.MobSnapshotTestingUtils;
43  import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
44  import org.apache.hadoop.hbase.testclassification.LargeTests;
45  import org.apache.hadoop.hbase.util.Bytes;
46  import org.junit.After;
47  import org.junit.AfterClass;
48  import org.junit.Assert;
49  import org.junit.Before;
50  import org.junit.BeforeClass;
51  import org.junit.Test;
52  import org.junit.experimental.categories.Category;
53  
54  /**
55   * Test to verify that the cloned table is independent of the table from which it was cloned
56   */
57  @Category(LargeTests.class)
58  public class TestMobSnapshotCloneIndependence {
59    private static final Log LOG = LogFactory.getLog(TestMobSnapshotCloneIndependence.class);
60  
61    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
62  
63    private static final int NUM_RS = 2;
64    private static final String STRING_TABLE_NAME = "test";
65    private static final String TEST_FAM_STR = "fam";
66    private static final byte[] TEST_FAM = Bytes.toBytes(TEST_FAM_STR);
67    private static final byte[] TABLE_NAME = Bytes.toBytes(STRING_TABLE_NAME);
68  
69    /**
70     * Setup the config for the cluster and start it
71     * @throws Exception on failure
72     */
73    @BeforeClass
74    public static void setupCluster() throws Exception {
75      setupConf(UTIL.getConfiguration());
76      UTIL.startMiniCluster(NUM_RS);
77    }
78  
79    private static void setupConf(Configuration conf) {
80      // enable snapshot support
81      conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
82      // disable the ui
83      conf.setInt("hbase.regionsever.info.port", -1);
84      // change the flush size to a small amount, regulating number of store files
85      conf.setInt("hbase.hregion.memstore.flush.size", 25000);
86      // so make sure we get a compaction when doing a load, but keep around
87      // some files in the store
88      conf.setInt("hbase.hstore.compaction.min", 10);
89      conf.setInt("hbase.hstore.compactionThreshold", 10);
90      // block writes if we get to 12 store files
91      conf.setInt("hbase.hstore.blockingStoreFiles", 12);
92      conf.setInt("hbase.regionserver.msginterval", 100);
93      conf.setBoolean("hbase.master.enabletable.roundrobin", true);
94      // Avoid potentially aggressive splitting which would cause snapshot to fail
95      conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
96        ConstantSizeRegionSplitPolicy.class.getName());
97      conf.setInt(MobConstants.MOB_FILE_CACHE_SIZE_KEY, 0);
98      conf.setInt("hfile.format.version", 3);
99      conf.set(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, SnapshotHFileCleaner.class.getName() + ","
100       + HFileLinkCleaner.class.getName());
101   }
102 
103   @Before
104   public void setup() throws Exception {
105     MobSnapshotTestingUtils.createMobTable(UTIL, TableName.valueOf(STRING_TABLE_NAME), TEST_FAM);
106   }
107 
108   @After
109   public void tearDown() throws Exception {
110     UTIL.deleteTable(TABLE_NAME);
111     SnapshotTestingUtils.deleteAllSnapshots(UTIL.getHBaseAdmin());
112     SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
113   }
114 
115   @AfterClass
116   public static void cleanupTest() throws Exception {
117     try {
118       UTIL.shutdownMiniCluster();
119     } catch (Exception e) {
120       LOG.warn("failure shutting down cluster", e);
121     }
122   }
123 
124   /**
125    * Verify that adding data to the cloned table will not affect the original, and vice-versa when
126    * it is taken as an online snapshot.
127    */
128   @Test (timeout=300000)
129   public void testOnlineSnapshotAppendIndependent() throws Exception {
130     runTestSnapshotAppendIndependent(true);
131   }
132 
133   /**
134    * Verify that adding data to the cloned table will not affect the original, and vice-versa when
135    * it is taken as an offline snapshot.
136    */
137   @Test (timeout=300000)
138   public void testOfflineSnapshotAppendIndependent() throws Exception {
139     runTestSnapshotAppendIndependent(false);
140   }
141 
142   /**
143    * Verify that adding metadata to the cloned table will not affect the original, and vice-versa
144    * when it is taken as an online snapshot.
145    */
146   @Test (timeout=300000)
147   public void testOnlineSnapshotMetadataChangesIndependent() throws Exception {
148     runTestSnapshotMetadataChangesIndependent(true);
149   }
150 
151   /**
152    * Verify that adding netadata to the cloned table will not affect the original, and vice-versa
153    * when is taken as an online snapshot.
154    */
155   @Test (timeout=300000)
156   public void testOfflineSnapshotMetadataChangesIndependent() throws Exception {
157     runTestSnapshotMetadataChangesIndependent(false);
158   }
159 
160   /**
161    * Verify that region operations, in this case splitting a region, are independent between the
162    * cloned table and the original.
163    */
164   @Test (timeout=300000)
165   public void testOfflineSnapshotRegionOperationsIndependent() throws Exception {
166     runTestRegionOperationsIndependent(false);
167   }
168 
169   /**
170    * Verify that region operations, in this case splitting a region, are independent between the
171    * cloned table and the original.
172    */
173   @Test (timeout=300000)
174   public void testOnlineSnapshotRegionOperationsIndependent() throws Exception {
175     runTestRegionOperationsIndependent(true);
176   }
177 
178   /**
179    * Verify the mob cells still exist after the table to be cloned is deleted.
180    */
181   @Test (timeout=300000)
182   public void testDeleteTableToBeCloned() throws Exception {
183     FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
184     Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
185     TableName tn = TableName.valueOf("testDeleteTableToBeCloned");
186     byte[] qf = Bytes.toBytes("qf");
187     MobSnapshotTestingUtils.createMobTable(UTIL, tn, TEST_FAM);
188     String row = "row";
189     String value = "value";
190     Put put = new Put(Bytes.toBytes(row));
191     put.addColumn(TEST_FAM, qf, Bytes.toBytes(value));
192     Admin admin = UTIL.getHBaseAdmin();
193     BufferedMutator mutator = UTIL.getConnection().getBufferedMutator(tn);
194     mutator.mutate(put);
195     mutator.flush();
196     admin.flush(tn);
197     // Take a snapshot
198     final String snapshotNameAsString = "snapshot_" + tn;
199     byte[] snapshotName = Bytes.toBytes(snapshotNameAsString);
200     Table table = ConnectionFactory.createConnection(UTIL.getConfiguration()).getTable(tn);
201     Table clonedTable = null;
202     try {
203       SnapshotTestingUtils.createSnapshotAndValidate(admin, tn, TEST_FAM_STR, snapshotNameAsString,
204         rootDir, fs, true);
205       TableName cloneTableName = TableName.valueOf("test-clone-" + tn);
206       admin.cloneSnapshot(snapshotName, cloneTableName);
207       clonedTable = ConnectionFactory.createConnection(UTIL.getConfiguration()).getTable(
208         cloneTableName);
209       admin.deleteSnapshot(snapshotName);
210       admin.disableTable(tn);
211       admin.deleteTable(tn);
212       // run the cleaner
213       UTIL.getHBaseCluster().getMaster().getHFileCleaner().choreForTesting();
214       // make sure the mob cell exists
215       Scan scan = new Scan();
216       ResultScanner scanner = clonedTable.getScanner(scan);
217       Result rs = scanner.next();
218       Cell cell = rs.getColumnLatestCell(TEST_FAM, qf);
219       Assert.assertEquals(value, Bytes.toString(CellUtil.cloneValue(cell)));
220       Assert.assertNull(scanner.next());
221     } finally {
222       table.close();
223       if (clonedTable != null) {
224         clonedTable.close();
225       }
226     }
227   }
228 
229   private static void waitOnSplit(final HTable t, int originalCount) throws Exception {
230     for (int i = 0; i < 200; i++) {
231       try {
232         Thread.sleep(50);
233       } catch (InterruptedException e) {
234         // Restore the interrupted status
235         Thread.currentThread().interrupt();
236       }
237       if (t.getRegionLocations().size() > originalCount) {
238         return;
239       }
240     }
241     throw new Exception("Split did not increase the number of regions");
242   }
243 
244   /*
245    * Take a snapshot of a table, add data, and verify that this only
246    * affects one table
247    * @param online - Whether the table is online or not during the snapshot
248    */
249   private void runTestSnapshotAppendIndependent(boolean online) throws Exception {
250     FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
251     Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
252 
253     Admin admin = UTIL.getHBaseAdmin();
254     final long startTime = System.currentTimeMillis();
255     final TableName localTableName =
256         TableName.valueOf(STRING_TABLE_NAME + startTime);
257 
258     HTable original = MobSnapshotTestingUtils.createMobTable(UTIL, localTableName, TEST_FAM);
259     try {
260 
261       SnapshotTestingUtils.loadData(UTIL, localTableName, 500, TEST_FAM);
262       final int origTableRowCount = MobSnapshotTestingUtils.countMobRows(original);
263 
264       // Take a snapshot
265       final String snapshotNameAsString = "snapshot_" + localTableName;
266       byte[] snapshotName = Bytes.toBytes(snapshotNameAsString);
267 
268       SnapshotTestingUtils.createSnapshotAndValidate(admin, localTableName, TEST_FAM_STR,
269         snapshotNameAsString, rootDir, fs, online);
270 
271       if (!online) {
272         admin.enableTable(localTableName);
273       }
274       TableName cloneTableName = TableName.valueOf("test-clone-" + localTableName);
275       admin.cloneSnapshot(snapshotName, cloneTableName);
276 
277       HTable clonedTable = new HTable(UTIL.getConfiguration(), cloneTableName);
278 
279       try {
280         final int clonedTableRowCount = MobSnapshotTestingUtils.countMobRows(clonedTable);
281 
282         Assert.assertEquals(
283           "The line counts of original and cloned tables do not match after clone. ",
284           origTableRowCount, clonedTableRowCount);
285 
286         // Attempt to add data to the test
287         final String rowKey = "new-row-" + System.currentTimeMillis();
288 
289         Put p = new Put(Bytes.toBytes(rowKey));
290         p.add(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString"));
291         original.put(p);
292         original.flushCommits();
293 
294         // Verify that it is not present in the original table
295         Assert.assertEquals("The row count of the original table was not modified by the put",
296           origTableRowCount + 1, MobSnapshotTestingUtils.countMobRows(original));
297         Assert.assertEquals(
298           "The row count of the cloned table changed as a result of addition to the original",
299           clonedTableRowCount, MobSnapshotTestingUtils.countMobRows(clonedTable));
300 
301         p = new Put(Bytes.toBytes(rowKey));
302         p.add(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString"));
303         clonedTable.put(p);
304         clonedTable.flushCommits();
305 
306         // Verify that the new family is not in the restored table's description
307         Assert.assertEquals(
308           "The row count of the original table was modified by the put to the clone",
309           origTableRowCount + 1, MobSnapshotTestingUtils.countMobRows(original));
310         Assert.assertEquals("The row count of the cloned table was not modified by the put",
311           clonedTableRowCount + 1, MobSnapshotTestingUtils.countMobRows(clonedTable));
312       } finally {
313 
314         clonedTable.close();
315       }
316     } finally {
317 
318       original.close();
319     }
320   }
321 
322   /*
323    * Take a snapshot of a table, do a split, and verify that this only affects one table
324    * @param online - Whether the table is online or not during the snapshot
325    */
326   private void runTestRegionOperationsIndependent(boolean online) throws Exception {
327     FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
328     Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
329 
330     // Create a table
331     Admin admin = UTIL.getHBaseAdmin();
332     final long startTime = System.currentTimeMillis();
333     final TableName localTableName =
334         TableName.valueOf(STRING_TABLE_NAME + startTime);
335     HTable original = MobSnapshotTestingUtils.createMobTable(UTIL, localTableName, TEST_FAM);
336     SnapshotTestingUtils.loadData(UTIL, localTableName, 500, TEST_FAM);
337     final int loadedTableCount = MobSnapshotTestingUtils.countMobRows(original);
338     System.out.println("Original table has: " + loadedTableCount + " rows");
339 
340     final String snapshotNameAsString = "snapshot_" + localTableName;
341 
342     // Create a snapshot
343     SnapshotTestingUtils.createSnapshotAndValidate(admin, localTableName, TEST_FAM_STR,
344       snapshotNameAsString, rootDir, fs, online);
345 
346     if (!online) {
347       admin.enableTable(localTableName);
348     }
349 
350     TableName cloneTableName = TableName.valueOf("test-clone-" + localTableName);
351 
352     // Clone the snapshot
353     byte[] snapshotName = Bytes.toBytes(snapshotNameAsString);
354     admin.cloneSnapshot(snapshotName, cloneTableName);
355 
356     // Verify that region information is the same pre-split
357     original.clearRegionCache();
358     List<HRegionInfo> originalTableHRegions = admin.getTableRegions(localTableName);
359 
360     final int originalRegionCount = originalTableHRegions.size();
361     final int cloneTableRegionCount = admin.getTableRegions(cloneTableName).size();
362     Assert.assertEquals(
363       "The number of regions in the cloned table is different than in the original table.",
364       originalRegionCount, cloneTableRegionCount);
365 
366     // Split a region on the parent table
367     admin.splitRegion(originalTableHRegions.get(0).getRegionName());
368     waitOnSplit(original, originalRegionCount);
369 
370     // Verify that the cloned table region is not split
371     final int cloneTableRegionCount2 = admin.getTableRegions(cloneTableName).size();
372     Assert.assertEquals(
373       "The number of regions in the cloned table changed though none of its regions were split.",
374       cloneTableRegionCount, cloneTableRegionCount2);
375   }
376 
377   /*
378    * Take a snapshot of a table, add metadata, and verify that this only
379    * affects one table
380    * @param online - Whether the table is online or not during the snapshot
381    */
382   private void runTestSnapshotMetadataChangesIndependent(boolean online) throws Exception {
383     FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
384     Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
385 
386     // Create a table
387     Admin admin = UTIL.getHBaseAdmin();
388     final long startTime = System.currentTimeMillis();
389     final TableName localTableName =
390         TableName.valueOf(STRING_TABLE_NAME + startTime);
391     HTable original = MobSnapshotTestingUtils.createMobTable(UTIL, localTableName, TEST_FAM);
392     SnapshotTestingUtils.loadData(UTIL, localTableName, 500, TEST_FAM);
393 
394     final String snapshotNameAsString = "snapshot_" + localTableName;
395 
396     // Create a snapshot
397     SnapshotTestingUtils.createSnapshotAndValidate(admin, localTableName, TEST_FAM_STR,
398       snapshotNameAsString, rootDir, fs, online);
399 
400     if (!online) {
401       admin.enableTable(localTableName);
402     }
403     TableName cloneTableName = TableName.valueOf("test-clone-" + localTableName);
404 
405     // Clone the snapshot
406     byte[] snapshotName = Bytes.toBytes(snapshotNameAsString);
407     admin.cloneSnapshot(snapshotName, cloneTableName);
408 
409     // Add a new column family to the original table
410     byte[] TEST_FAM_2 = Bytes.toBytes("fam2");
411     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAM_2);
412 
413     admin.disableTable(localTableName);
414     admin.addColumn(localTableName, hcd);
415 
416     // Verify that it is not in the snapshot
417     admin.enableTable(localTableName);
418 
419     // get a description of the cloned table
420     // get a list of its families
421     // assert that the family is there
422     HTableDescriptor originalTableDescriptor = original.getTableDescriptor();
423     HTableDescriptor clonedTableDescriptor = admin.getTableDescriptor(cloneTableName);
424 
425     Assert.assertTrue("The original family was not found. There is something wrong. ",
426       originalTableDescriptor.hasFamily(TEST_FAM));
427     Assert.assertTrue("The original family was not found in the clone. There is something wrong. ",
428       clonedTableDescriptor.hasFamily(TEST_FAM));
429 
430     Assert.assertTrue("The new family was not found. ",
431       originalTableDescriptor.hasFamily(TEST_FAM_2));
432     Assert.assertTrue("The new family was not found. ",
433       !clonedTableDescriptor.hasFamily(TEST_FAM_2));
434   }
435 }