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