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  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.conf.Configuration;
25  import org.apache.hadoop.fs.FileSystem;
26  import org.apache.hadoop.fs.Path;
27  import org.apache.hadoop.hbase.HBaseTestingUtility;
28  import org.apache.hadoop.hbase.HColumnDescriptor;
29  import org.apache.hadoop.hbase.HConstants;
30  import org.apache.hadoop.hbase.HRegionInfo;
31  import org.apache.hadoop.hbase.HTableDescriptor;
32  import org.apache.hadoop.hbase.TableName;
33  import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
34  import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
35  import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
36  import org.apache.hadoop.hbase.testclassification.ClientTests;
37  import org.apache.hadoop.hbase.testclassification.MediumTests;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.hbase.util.Threads;
40  import org.junit.AfterClass;
41  import org.junit.Assert;
42  import org.junit.Before;
43  import org.junit.BeforeClass;
44  import org.junit.Rule;
45  import org.junit.Test;
46  import org.junit.experimental.categories.Category;
47  import org.junit.rules.TestName;
48  import org.junit.rules.Timeout;
49  
50  /**
51   * Test to verify that the cloned table is independent of the table from which it was cloned
52   */
53  @Category({MediumTests.class, ClientTests.class})
54  public class TestSnapshotCloneIndependence {
55    private static final Log LOG = LogFactory.getLog(TestSnapshotCloneIndependence.class);
56  
57    @Rule
58    public Timeout globalTimeout = Timeout.seconds(60);
59  
60    @Rule
61    public TestName testName = new TestName();
62  
63    protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
64  
65    protected static final int NUM_RS = 2;
66    private static final String STRING_TABLE_NAME = "test";
67    private static final String TEST_FAM_STR = "fam";
68    protected static final byte[] TEST_FAM = Bytes.toBytes(TEST_FAM_STR);
69    private static final int CLEANER_INTERVAL = 100;
70  
71    private FileSystem fs;
72    private Path rootDir;
73    private Admin admin;
74    private TableName originalTableName;
75    private Table originalTable;
76    private TableName cloneTableName;
77    private int countOriginalTable;
78    String snapshotNameAsString;
79    byte[] snapshotName;
80  
81    /**
82     * Setup the config for the cluster and start it
83     */
84    @BeforeClass
85    public static void setupCluster() throws Exception {
86      setupConf(UTIL.getConfiguration());
87      UTIL.startMiniCluster(NUM_RS);
88    }
89  
90    static void setupConf(Configuration conf) {
91      // Up the handlers; this test needs more than usual.
92      conf.setInt(HConstants.REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT, 15);
93      // enable snapshot support
94      conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
95      // disable the ui
96      conf.setInt("hbase.regionsever.info.port", -1);
97      conf.setInt("hbase.master.info.port", -1);
98      // change the flush size to a small amount, regulating number of store files
99      conf.setInt("hbase.hregion.memstore.flush.size", 25000);
100     // so make sure we get a compaction when doing a load, but keep around
101     // some files in the store
102     conf.setInt("hbase.hstore.compaction.min", 10);
103     conf.setInt("hbase.hstore.compactionThreshold", 10);
104     // block writes if we get to 12 store files
105     conf.setInt("hbase.hstore.blockingStoreFiles", 12);
106     conf.setInt("hbase.regionserver.msginterval", 100);
107     conf.setBoolean("hbase.master.enabletable.roundrobin", true);
108     // Avoid potentially aggressive splitting which would cause snapshot to fail
109     conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
110         ConstantSizeRegionSplitPolicy.class.getName());
111     // Execute cleaner frequently to induce failures
112     conf.setInt("hbase.master.cleaner.interval", CLEANER_INTERVAL);
113     conf.setInt("hbase.master.hfilecleaner.plugins.snapshot.period", CLEANER_INTERVAL);
114     // Effectively disable TimeToLiveHFileCleaner. Don't want to fully disable it because that
115     // will even trigger races between creating the directory containing back references and
116     // the back reference itself.
117     conf.setInt("hbase.master.hfilecleaner.ttl", CLEANER_INTERVAL);
118   }
119 
120   @Before
121   public void setup() throws Exception {
122     fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
123     rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
124 
125     admin = UTIL.getHBaseAdmin();
126     originalTableName = TableName.valueOf("test" + testName.getMethodName());
127     cloneTableName = TableName.valueOf("test-clone-" + originalTableName);
128     snapshotNameAsString = "snapshot_" + originalTableName;
129     snapshotName = Bytes.toBytes(snapshotNameAsString);
130 
131     originalTable = createTable(originalTableName, TEST_FAM);
132     loadData(originalTable, TEST_FAM);
133     countOriginalTable = countRows(originalTable);
134     System.out.println("Original table has: " + countOriginalTable + " rows");
135   }
136 
137   private void tearDown() throws Exception {
138     UTIL.deleteTable(originalTableName);
139     UTIL.deleteTable(cloneTableName);
140     SnapshotTestingUtils.deleteAllSnapshots(UTIL.getHBaseAdmin());
141     SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
142   }
143 
144   @AfterClass
145   public static void cleanupTest() throws Exception {
146     try {
147       UTIL.shutdownMiniCluster();
148     } catch (Exception e) {
149       LOG.warn("failure shutting down cluster", e);
150     }
151   }
152 
153   /**
154    * Verify that adding data to the cloned table will not affect the original, and vice-versa when
155    * it is taken as an online snapshot.
156    */
157   @Test(timeout = 30000)
158   public void testOnlineSnapshotAppendIndependent() throws Exception {
159     createAndCloneSnapshot(true);
160     runTestSnapshotAppendIndependent();
161     tearDown();
162   }
163 
164   /**
165    * Verify that adding data to the cloned table will not affect the original, and vice-versa when
166    * it is taken as an offline snapshot.
167    */
168   @Test(timeout = 30000)
169   public void testOfflineSnapshotAppendIndependent() throws Exception {
170     createAndCloneSnapshot(false);
171     runTestSnapshotAppendIndependent();
172     tearDown();
173   }
174 
175   /**
176    * Verify that adding metadata to the cloned table will not affect the original, and vice-versa
177    * when it is taken as an online snapshot.
178    */
179   @Test(timeout = 30000)
180   public void testOnlineSnapshotMetadataChangesIndependent() throws Exception {
181     createAndCloneSnapshot(true);
182     runTestSnapshotMetadataChangesIndependent();
183     tearDown();
184   }
185 
186   /**
187    * Verify that adding netadata to the cloned table will not affect the original, and vice-versa
188    * when is taken as an online snapshot.
189    */
190   @Test(timeout = 30000)
191   public void testOfflineSnapshotMetadataChangesIndependent() throws Exception {
192     createAndCloneSnapshot(false);
193     runTestSnapshotMetadataChangesIndependent();
194     tearDown();
195   }
196 
197   /**
198    * Verify that region operations, in this case splitting a region, are independent between the
199    * cloned table and the original.
200    */
201   @Test(timeout = 30000)
202   public void testOfflineSnapshotRegionOperationsIndependent() throws Exception {
203     createAndCloneSnapshot(false);
204     runTestRegionOperationsIndependent();
205     tearDown();
206   }
207 
208   /**
209    * Verify that region operations, in this case splitting a region, are independent between the
210    * cloned table and the original.
211    */
212   @Test(timeout = 30000)
213   public void testOnlineSnapshotRegionOperationsIndependent() throws Exception {
214     createAndCloneSnapshot(true);
215     runTestRegionOperationsIndependent();
216     tearDown();
217   }
218 
219   @Test(timeout = 30000)
220   public void testOfflineSnapshotDeleteIndependent() throws Exception {
221     createAndCloneSnapshot(false);
222     runTestSnapshotDeleteIndependent();
223     tearDown();
224   }
225 
226   @Test(timeout = 30000)
227   public void testOnlineSnapshotDeleteIndependent() throws Exception {
228     createAndCloneSnapshot(true);
229     runTestSnapshotDeleteIndependent();
230     tearDown();
231   }
232 
233   private static void waitOnSplit(Connection c, final Table t, int originalCount) throws Exception {
234     for (int i = 0; i < 200; i++) {
235       Threads.sleepWithoutInterrupt(500);
236       try (RegionLocator locator = c.getRegionLocator(t.getName())) {
237         if (locator.getAllRegionLocations().size() > originalCount) {
238           return;
239         }
240       }
241     }
242     throw new Exception("Split did not increase the number of regions");
243   }
244 
245   /**
246    * Takes the snapshot of originalTable and clones the snapshot to another tables.
247    * If {@code online} is false, the original table is disabled during taking snapshot, so also
248    * enables it again.
249    * @param online - Whether the table is online or not during the snapshot
250    */
251   private void createAndCloneSnapshot(boolean online) throws Exception {
252     SnapshotTestingUtils.createSnapshotAndValidate(admin, originalTableName, TEST_FAM_STR,
253       snapshotNameAsString, rootDir, fs, online);
254 
255     // If offline, enable the table disabled by snapshot testing util.
256     if (!online) {
257       admin.enableTable(originalTableName);
258       UTIL.waitTableAvailable(originalTableName);
259     }
260 
261     admin.cloneSnapshot(snapshotName, cloneTableName);
262     UTIL.waitUntilAllRegionsAssigned(cloneTableName);
263   }
264 
265   /**
266    * Verify that adding data to original table or clone table doesn't affect other table.
267    */
268   private void runTestSnapshotAppendIndependent() throws Exception {
269     try (Table clonedTable = UTIL.getConnection().getTable(cloneTableName)) {
270       final int clonedTableRowCount = countRows(clonedTable);
271 
272       Assert.assertEquals(
273         "The line counts of original and cloned tables do not match after clone. ",
274         countOriginalTable, clonedTableRowCount);
275 
276       // Attempt to add data to the test
277       Put p = new Put(Bytes.toBytes("new-row-" + System.currentTimeMillis()));
278       p.addColumn(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString"));
279       originalTable.put(p);
280 
281       // Verify that the new row is not in the restored table
282       Assert.assertEquals("The row count of the original table was not modified by the put",
283         countOriginalTable + 1, countRows(originalTable));
284       Assert.assertEquals(
285         "The row count of the cloned table changed as a result of addition to the original",
286         clonedTableRowCount, countRows(clonedTable));
287 
288       Put p2 = new Put(Bytes.toBytes("new-row-" + System.currentTimeMillis()));
289       p2.addColumn(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString"));
290       clonedTable.put(p2);
291 
292       // Verify that the row is not added to the original table.
293       Assert.assertEquals(
294         "The row count of the original table was modified by the put to the clone",
295         countOriginalTable + 1, countRows(originalTable));
296       Assert.assertEquals("The row count of the cloned table was not modified by the put",
297         clonedTableRowCount + 1, countRows(clonedTable));
298     }
299   }
300 
301   /**
302    * Do a split, and verify that this only affects one table
303    */
304   private void runTestRegionOperationsIndependent() throws Exception {
305     // Verify that region information is the same pre-split
306     ((ClusterConnection) UTIL.getConnection()).clearRegionCache();
307     List<HRegionInfo> originalTableHRegions = admin.getTableRegions(originalTableName);
308 
309     final int originalRegionCount = originalTableHRegions.size();
310     final int cloneTableRegionCount = admin.getTableRegions(cloneTableName).size();
311     Assert.assertEquals(
312       "The number of regions in the cloned table is different than in the original table.",
313       originalRegionCount, cloneTableRegionCount);
314 
315     // Split a region on the parent table
316     admin.splitRegion(originalTableHRegions.get(0).getRegionName());
317     waitOnSplit(UTIL.getConnection(), originalTable, originalRegionCount);
318 
319     // Verify that the cloned table region is not split
320     final int cloneTableRegionCount2 = admin.getTableRegions(cloneTableName).size();
321     Assert.assertEquals(
322       "The number of regions in the cloned table changed though none of its regions were split.",
323       cloneTableRegionCount, cloneTableRegionCount2);
324   }
325 
326   /**
327    * Add metadata, and verify that this only affects one table
328    */
329   private void runTestSnapshotMetadataChangesIndependent() throws Exception {
330     // Add a new column family to the original table
331     byte[] TEST_FAM_2 = Bytes.toBytes("fam2");
332     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAM_2);
333 
334     admin.disableTable(originalTableName);
335     admin.addColumn(originalTableName, hcd);
336 
337     // Verify that it is not in the snapshot
338     admin.enableTable(originalTableName);
339     UTIL.waitTableAvailable(originalTableName);
340 
341     // get a description of the cloned table
342     // get a list of its families
343     // assert that the family is there
344     HTableDescriptor originalTableDescriptor = originalTable.getTableDescriptor();
345     HTableDescriptor clonedTableDescriptor = admin.getTableDescriptor(cloneTableName);
346 
347     Assert.assertTrue("The original family was not found. There is something wrong. ",
348       originalTableDescriptor.hasFamily(TEST_FAM));
349     Assert.assertTrue("The original family was not found in the clone. There is something wrong. ",
350       clonedTableDescriptor.hasFamily(TEST_FAM));
351 
352     Assert.assertTrue("The new family was not found. ",
353       originalTableDescriptor.hasFamily(TEST_FAM_2));
354     Assert.assertTrue("The new family was not found. ",
355       !clonedTableDescriptor.hasFamily(TEST_FAM_2));
356   }
357 
358   /**
359    * Verify that deleting the snapshot does not affect either table.
360    */
361   private void runTestSnapshotDeleteIndependent() throws Exception {
362     // Ensure the original table does not reference the HFiles anymore
363     admin.majorCompact(originalTableName);
364 
365     // Deleting the snapshot used to break the cloned table by deleting in-use HFiles
366     admin.deleteSnapshot(snapshotName);
367 
368     // Wait for cleaner run and DFS heartbeats so that anything that is deletable is fully deleted
369     do {
370       Thread.sleep(5000);
371     } while (!admin.listSnapshots(snapshotNameAsString).isEmpty());
372 
373     try (Table original = UTIL.getConnection().getTable(originalTableName)) {
374       try (Table clonedTable = UTIL.getConnection().getTable(cloneTableName)) {
375         // Verify that all regions of both tables are readable
376         final int origTableRowCount = countRows(original);
377         final int clonedTableRowCount = countRows(clonedTable);
378         Assert.assertEquals(origTableRowCount, clonedTableRowCount);
379       }
380     }
381   }
382 
383   protected Table createTable(final TableName table, byte[] family) throws Exception {
384    Table t = UTIL.createTable(table, family);
385     // Wait for everything to be ready with the table
386     UTIL.waitUntilAllRegionsAssigned(table);
387 
388     // At this point the table should be good to go.
389     return t;
390   }
391 
392   public void loadData(final Table table, byte[]... families) throws Exception {
393     UTIL.loadTable(originalTable, TEST_FAM);
394   }
395 
396   protected int countRows(final Table table, final byte[]... families) throws Exception {
397     return UTIL.countRows(table, families);
398   }
399 }