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