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    * <p>
10   * http://www.apache.org/licenses/LICENSE-2.0
11   * <p>
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.client;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  
24  import com.google.common.collect.Lists;
25  import java.io.File;
26  import java.io.IOException;
27  import java.nio.file.Paths;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.UUID;
33  import org.apache.commons.io.FileUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.fs.FileSystem;
38  import org.apache.hadoop.fs.Path;
39  import org.apache.hadoop.hbase.HBaseTestingUtility;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.HTableDescriptor;
42  import org.apache.hadoop.hbase.TableName;
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.protobuf.generated.HBaseProtos.SnapshotDescription.Type;
46  import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
47  import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
48  import org.apache.hadoop.hbase.snapshot.SnapshotDoesNotExistException;
49  import org.apache.hadoop.hbase.snapshot.SnapshotManifestV1;
50  import org.apache.hadoop.hbase.snapshot.SnapshotManifestV2;
51  import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
52  import org.apache.hadoop.hbase.testclassification.LargeTests;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.apache.hadoop.hbase.util.FSUtils;
55  import org.junit.After;
56  import org.junit.AfterClass;
57  import org.junit.Before;
58  import org.junit.BeforeClass;
59  import org.junit.Test;
60  import org.junit.experimental.categories.Category;
61  import org.junit.runner.RunWith;
62  import org.junit.runners.Parameterized;
63  
64  /**
65   * This class tests that the use of a temporary snapshot directory supports snapshot functionality
66   * while the temporary directory is on a different file system than the root directory
67   * <p>
68   * This is an end-to-end test for the snapshot utility
69   */
70  @Category(LargeTests.class)
71  @RunWith(Parameterized.class)
72  public class TestSnapshotTemporaryDirectory {
73  
74    @Parameterized.Parameters public static Iterable<Integer> data() {
75      return Arrays
76          .asList(SnapshotManifestV1.DESCRIPTOR_VERSION, SnapshotManifestV2.DESCRIPTOR_VERSION);
77    }
78  
79    @Parameterized.Parameter public int manifestVersion;
80  
81    private static final Log LOG = LogFactory.getLog(TestSnapshotTemporaryDirectory.class);
82    protected static final int NUM_RS = 2;
83    protected static String TEMP_DIR =
84        Paths.get("").toAbsolutePath().toString() + Path.SEPARATOR + UUID.randomUUID().toString();
85  
86    protected static Admin admin;
87    protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
88    protected static final String STRING_TABLE_NAME = "test";
89    protected static final byte[] TEST_FAM = Bytes.toBytes("fam");
90    protected static final TableName TABLE_NAME = TableName.valueOf(STRING_TABLE_NAME);
91  
92    /**
93     * Setup the config for the cluster
94     *
95     * @throws Exception on failure
96     */
97    @BeforeClass public static void setupCluster() throws Exception {
98      setupConf(UTIL.getConfiguration());
99      UTIL.startMiniCluster(NUM_RS);
100     admin = UTIL.getHBaseAdmin();
101   }
102 
103   private static void setupConf(Configuration conf) {
104     // disable the ui
105     conf.setInt("hbase.regionsever.info.port", -1);
106     // change the flush size to a small amount, regulating number of store files
107     conf.setInt("hbase.hregion.memstore.flush.size", 25000);
108     // so make sure we get a compaction when doing a load, but keep around some
109     // files in the store
110     conf.setInt("hbase.hstore.compaction.min", 10);
111     conf.setInt("hbase.hstore.compactionThreshold", 10);
112     // block writes if we get to 12 store files
113     conf.setInt("hbase.hstore.blockingStoreFiles", 12);
114     // Enable snapshot
115     conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
116     conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
117         ConstantSizeRegionSplitPolicy.class.getName());
118     conf.set(SnapshotDescriptionUtils.SNAPSHOT_WORKING_DIR, "file://" + new Path(TEMP_DIR, ".tmpDir").toUri());
119   }
120 
121   @Before public void setup() throws Exception {
122     HTableDescriptor htd = new HTableDescriptor(TABLE_NAME);
123     htd.setRegionReplication(getNumReplicas());
124     UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration());
125   }
126 
127   protected int getNumReplicas() {
128     return 1;
129   }
130 
131   @After public void tearDown() throws Exception {
132     UTIL.deleteTable(TABLE_NAME);
133     SnapshotTestingUtils.deleteAllSnapshots(UTIL.getHBaseAdmin());
134     SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
135   }
136 
137   @AfterClass public static void cleanupTest() {
138     try {
139       UTIL.shutdownMiniCluster();
140       FileUtils.deleteDirectory(new File(TEMP_DIR));
141     } catch (Exception e) {
142       LOG.warn("failure shutting down cluster", e);
143     }
144   }
145 
146   @Test(timeout = 180000) public void testRestoreDisabledSnapshot()
147       throws IOException, InterruptedException {
148     long tid = System.currentTimeMillis();
149     TableName tableName = TableName.valueOf("testtb-" + tid);
150     byte[] emptySnapshot = Bytes.toBytes("emptySnaptb-" + tid);
151     byte[] snapshotName0 = Bytes.toBytes("snaptb0-" + tid);
152     byte[] snapshotName1 = Bytes.toBytes("snaptb1-" + tid);
153     int snapshot0Rows;
154     int snapshot1Rows;
155 
156     // create Table and disable it
157     SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM);
158     admin.disableTable(tableName);
159 
160     // take an empty snapshot
161     takeSnapshot(tableName, Bytes.toString(emptySnapshot), true);
162 
163     // enable table and insert data
164     admin.enableTable(tableName);
165     SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
166     try (Table table = UTIL.getConnection().getTable(tableName)) {
167       snapshot0Rows = UTIL.countRows(table);
168     }
169     admin.disableTable(tableName);
170 
171     // take a snapshot
172     takeSnapshot(tableName, Bytes.toString(snapshotName0), true);
173 
174     // enable table and insert more data
175     admin.enableTable(tableName);
176     SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
177     try (Table table = UTIL.getConnection().getTable(tableName)) {
178       snapshot1Rows = UTIL.countRows(table);
179     }
180 
181     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
182     admin.disableTable(tableName);
183     takeSnapshot(tableName, Bytes.toString(snapshotName1), true);
184 
185     // Restore from snapshot-0
186     admin.restoreSnapshot(snapshotName0);
187     admin.enableTable(tableName);
188     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows);
189     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
190 
191     // Restore from emptySnapshot
192     admin.disableTable(tableName);
193     admin.restoreSnapshot(emptySnapshot);
194     admin.enableTable(tableName);
195     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0);
196     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
197 
198     // Restore from snapshot-1
199     admin.disableTable(tableName);
200     admin.restoreSnapshot(snapshotName1);
201     admin.enableTable(tableName);
202     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
203     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
204 
205     // Restore from snapshot-1
206     UTIL.deleteTable(tableName);
207     admin.restoreSnapshot(snapshotName1);
208     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
209     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
210   }
211 
212   @Test(timeout = 180000) public void testRestoreEnabledSnapshot()
213       throws IOException, InterruptedException {
214     long tid = System.currentTimeMillis();
215     TableName tableName = TableName.valueOf("testtb-" + tid);
216     byte[] emptySnapshot = Bytes.toBytes("emptySnaptb-" + tid);
217     byte[] snapshotName0 = Bytes.toBytes("snaptb0-" + tid);
218     byte[] snapshotName1 = Bytes.toBytes("snaptb1-" + tid);
219     int snapshot0Rows;
220     int snapshot1Rows;
221 
222     // create Table
223     SnapshotTestingUtils.createTable(UTIL, tableName, getNumReplicas(), TEST_FAM);
224 
225     // take an empty snapshot
226     takeSnapshot(tableName, Bytes.toString(emptySnapshot), false);
227 
228     // Insert data
229     SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
230     try (Table table = UTIL.getConnection().getTable(tableName)) {
231       snapshot0Rows = UTIL.countRows(table);
232     }
233 
234     // take a snapshot
235     takeSnapshot(tableName, Bytes.toString(snapshotName0), false);
236 
237     // Insert more data
238     SnapshotTestingUtils.loadData(UTIL, tableName, 500, TEST_FAM);
239     try (Table table = UTIL.getConnection().getTable(tableName)) {
240       snapshot1Rows = UTIL.countRows(table);
241     }
242 
243     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
244     takeSnapshot(tableName, Bytes.toString(snapshotName1), false);
245 
246     // Restore from snapshot-0
247     admin.disableTable(tableName);
248     admin.restoreSnapshot(snapshotName0);
249     admin.enableTable(tableName);
250     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot0Rows);
251     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
252 
253     // Restore from emptySnapshot
254     admin.disableTable(tableName);
255     admin.restoreSnapshot(emptySnapshot);
256     admin.enableTable(tableName);
257     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, 0);
258     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
259 
260     // Restore from snapshot-1
261     admin.disableTable(tableName);
262     admin.restoreSnapshot(snapshotName1);
263     admin.enableTable(tableName);
264     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
265     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
266 
267     // Restore from snapshot-1
268     UTIL.deleteTable(tableName);
269     admin.restoreSnapshot(snapshotName1);
270     SnapshotTestingUtils.verifyRowCount(UTIL, tableName, snapshot1Rows);
271     SnapshotTestingUtils.verifyReplicasCameOnline(tableName, admin, getNumReplicas());
272   }
273 
274   /**
275    * Test snapshotting a table that is offline
276    *
277    * @throws Exception if snapshot does not complete successfully
278    */
279   @Test(timeout = 300000) public void testOfflineTableSnapshot() throws Exception {
280     Admin admin = UTIL.getHBaseAdmin();
281     // make sure we don't fail on listing snapshots
282     SnapshotTestingUtils.assertNoSnapshots(admin);
283 
284     // put some stuff in the table
285     Table table = UTIL.getConnection().getTable(TABLE_NAME);
286     UTIL.loadTable(table, TEST_FAM, false);
287 
288     LOG.debug("FS state before disable:");
289     FSUtils
290         .logFileSystemState(UTIL.getTestFileSystem(), FSUtils.getRootDir(UTIL.getConfiguration()),
291             LOG);
292     // XXX if this is flakey, might want to consider using the async version and looping as
293     // disableTable can succeed and still timeout.
294     admin.disableTable(TABLE_NAME);
295 
296     LOG.debug("FS state before snapshot:");
297     FSUtils
298         .logFileSystemState(UTIL.getTestFileSystem(), FSUtils.getRootDir(UTIL.getConfiguration()),
299             LOG);
300 
301     // take a snapshot of the disabled table
302     final String SNAPSHOT_NAME = "offlineTableSnapshot";
303     byte[] snapshot = Bytes.toBytes(SNAPSHOT_NAME);
304     takeSnapshot(TABLE_NAME, SNAPSHOT_NAME, true);
305     LOG.debug("Snapshot completed.");
306 
307     // make sure we have the snapshot
308     List<SnapshotDescription> snapshots =
309         SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME);
310 
311     // make sure its a valid snapshot
312     FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
313     Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
314     LOG.debug("FS state after snapshot:");
315     FSUtils
316         .logFileSystemState(UTIL.getTestFileSystem(), FSUtils.getRootDir(UTIL.getConfiguration()),
317             LOG);
318 
319     SnapshotTestingUtils
320         .confirmSnapshotValid(snapshots.get(0), TABLE_NAME, TEST_FAM, rootDir, admin, fs);
321 
322     admin.deleteSnapshot(snapshot);
323     SnapshotTestingUtils.assertNoSnapshots(admin);
324   }
325 
326   /**
327    * Tests that snapshot has correct contents by taking snapshot, cloning it, then affirming
328    * the contents of the original and cloned table match
329    *
330    * @throws Exception if snapshot does not complete successfully
331    */
332   @Test(timeout = 180000) public void testSnapshotCloneContents() throws Exception {
333     // make sure we don't fail on listing snapshots
334     SnapshotTestingUtils.assertNoSnapshots(admin);
335 
336     // put some stuff in the table
337     Table table = UTIL.getConnection().getTable(TABLE_NAME);
338     UTIL.loadTable(table, TEST_FAM);
339     table.close();
340 
341     String snapshot1 = "TableSnapshot1";
342     takeSnapshot(TABLE_NAME, snapshot1, false);
343     LOG.debug("Snapshot1 completed.");
344 
345     TableName clone = TableName.valueOf("Table1Clone");
346     admin.cloneSnapshot(snapshot1, clone, false);
347 
348     Scan original = new Scan();
349     Scan cloned = new Scan();
350     ResultScanner originalScan = admin.getConnection().getTable(TABLE_NAME).getScanner(original);
351     ResultScanner clonedScan =
352         admin.getConnection().getTable(TableName.valueOf("Table1Clone")).getScanner(cloned);
353 
354     Iterator<Result> i = originalScan.iterator();
355     Iterator<Result> i2 = clonedScan.iterator();
356     assertTrue(i.hasNext());
357     while (i.hasNext()) {
358       assertTrue(i2.hasNext());
359       assertEquals(Bytes.toString(i.next().getValue(TEST_FAM, new byte[] {})),
360           Bytes.toString(i2.next().getValue(TEST_FAM, new byte[] {})));
361     }
362     assertFalse(i2.hasNext());
363     admin.deleteSnapshot(snapshot1);
364     UTIL.deleteTable(clone);
365     admin.close();
366   }
367 
368   @Test(timeout = 180000) public void testOfflineTableSnapshotWithEmptyRegion() throws Exception {
369     // test with an empty table with one region
370 
371     // make sure we don't fail on listing snapshots
372     SnapshotTestingUtils.assertNoSnapshots(admin);
373 
374     LOG.debug("FS state before disable:");
375     FSUtils
376         .logFileSystemState(UTIL.getTestFileSystem(), FSUtils.getRootDir(UTIL.getConfiguration()),
377             LOG);
378     admin.disableTable(TABLE_NAME);
379 
380     LOG.debug("FS state before snapshot:");
381     FSUtils
382         .logFileSystemState(UTIL.getTestFileSystem(), FSUtils.getRootDir(UTIL.getConfiguration()),
383             LOG);
384 
385     // take a snapshot of the disabled table
386     byte[] snapshot = Bytes.toBytes("testOfflineTableSnapshotWithEmptyRegion");
387     takeSnapshot(TABLE_NAME, Bytes.toString(snapshot), true);
388     LOG.debug("Snapshot completed.");
389 
390     // make sure we have the snapshot
391     List<SnapshotDescription> snapshots =
392         SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshot, TABLE_NAME);
393 
394     // make sure its a valid snapshot
395     FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
396     Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
397     LOG.debug("FS state after snapshot:");
398     FSUtils
399         .logFileSystemState(UTIL.getTestFileSystem(), FSUtils.getRootDir(UTIL.getConfiguration()),
400             LOG);
401 
402     List<byte[]> emptyCfs = Lists.newArrayList(TEST_FAM); // no file in the region
403     List<byte[]> nonEmptyCfs = Lists.newArrayList();
404     SnapshotTestingUtils
405         .confirmSnapshotValid(snapshots.get(0), TABLE_NAME, nonEmptyCfs, emptyCfs, rootDir,
406             admin, fs);
407 
408     admin.deleteSnapshot(snapshot);
409     SnapshotTestingUtils.assertNoSnapshots(admin);
410   }
411 
412   // Ensures that the snapshot is transferred to the proper completed snapshot directory
413   @Test(timeout = 180000) public void testEnsureTemporaryDirectoryTransfer() throws Exception {
414     Admin admin = null;
415     TableName tableName2 = TableName.valueOf("testListTableSnapshots");
416     try {
417       admin = UTIL.getHBaseAdmin();
418 
419       HTableDescriptor htd = new HTableDescriptor(tableName2);
420       UTIL.createTable(htd, new byte[][] { TEST_FAM }, UTIL.getConfiguration());
421 
422       String table1Snapshot1 = "Table1Snapshot1";
423       takeSnapshot(TABLE_NAME, table1Snapshot1, false);
424       LOG.debug("Snapshot1 completed.");
425 
426       String table1Snapshot2 = "Table1Snapshot2";
427       takeSnapshot(TABLE_NAME, table1Snapshot2, false);
428       LOG.debug("Snapshot2 completed.");
429 
430       String table2Snapshot1 = "Table2Snapshot1";
431       takeSnapshot(TABLE_NAME, table2Snapshot1, false);
432       LOG.debug("Table2Snapshot1 completed.");
433 
434       List<SnapshotDescription> listTableSnapshots = admin.listTableSnapshots("test.*", ".*");
435       List<String> listTableSnapshotNames = new ArrayList<String>();
436       assertEquals(3, listTableSnapshots.size());
437       for (SnapshotDescription s : listTableSnapshots) {
438         listTableSnapshotNames.add(s.getName());
439       }
440       assertTrue(listTableSnapshotNames.contains(table1Snapshot1));
441       assertTrue(listTableSnapshotNames.contains(table1Snapshot2));
442       assertTrue(listTableSnapshotNames.contains(table2Snapshot1));
443     } finally {
444       if (admin != null) {
445         try {
446           admin.deleteSnapshots("Table.*");
447         } catch (SnapshotDoesNotExistException ignore) {
448         }
449         if (admin.tableExists(tableName2)) {
450           UTIL.deleteTable(tableName2);
451         }
452         admin.close();
453       }
454     }
455   }
456 
457   private void takeSnapshot(TableName tableName, String snapshotName, boolean disabled)
458       throws IOException {
459     Type type = disabled ? Type.DISABLED : Type.FLUSH;
460     SnapshotDescription desc = SnapshotDescription.newBuilder()
461         .setTable(tableName.getNameAsString())
462         .setName(snapshotName)
463         .setVersion(manifestVersion)
464         .setType(type)
465         .build();
466     admin.snapshot(desc);
467   }
468 }