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.snapshot;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.io.IOException;
25  import java.net.URI;
26  import java.util.ArrayList;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.fs.FSDataOutputStream;
35  import org.apache.hadoop.fs.FileStatus;
36  import org.apache.hadoop.fs.FileSystem;
37  import org.apache.hadoop.fs.FileUtil;
38  import org.apache.hadoop.fs.Path;
39  import org.apache.hadoop.hbase.HBaseTestingUtility;
40  import org.apache.hadoop.hbase.HColumnDescriptor;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.HRegionInfo;
43  import org.apache.hadoop.hbase.HTableDescriptor;
44  import org.apache.hadoop.hbase.testclassification.MediumTests;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.hbase.client.HBaseAdmin;
47  import org.apache.hadoop.hbase.client.HTable;
48  import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
49  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
50  import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos.SnapshotFileInfo;
51  import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
52  import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.apache.hadoop.hbase.util.FSUtils;
55  import org.apache.hadoop.hbase.util.FSTableDescriptors;
56  import org.apache.hadoop.hbase.util.Pair;
57  import org.junit.After;
58  import org.junit.AfterClass;
59  import org.junit.Before;
60  import org.junit.BeforeClass;
61  import org.junit.Test;
62  import org.junit.experimental.categories.Category;
63  
64  /**
65   * Test Export Snapshot Tool
66   */
67  @Category(MediumTests.class)
68  public class TestExportSnapshot {
69    private final Log LOG = LogFactory.getLog(getClass());
70  
71    protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
72  
73    private final static byte[] FAMILY = Bytes.toBytes("cf");
74  
75    private byte[] emptySnapshotName;
76    private byte[] snapshotName;
77    private TableName tableName;
78    private HBaseAdmin admin;
79  
80    public static void setUpBaseConf(Configuration conf) {
81      conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
82      conf.setInt("hbase.regionserver.msginterval", 100);
83      conf.setInt("hbase.client.pause", 250);
84      conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
85      conf.setBoolean("hbase.master.enabletable.roundrobin", true);
86      conf.setInt("mapreduce.map.max.attempts", 10);
87      conf.setInt("mapred.map.max.attempts", 10);
88    }
89  
90    @BeforeClass
91    public static void setUpBeforeClass() throws Exception {
92      setUpBaseConf(TEST_UTIL.getConfiguration());
93      TEST_UTIL.startMiniCluster(3);
94      TEST_UTIL.startMiniMapReduceCluster();
95    }
96  
97    @AfterClass
98    public static void tearDownAfterClass() throws Exception {
99      TEST_UTIL.shutdownMiniMapReduceCluster();
100     TEST_UTIL.shutdownMiniCluster();
101   }
102 
103   /**
104    * Create a table and take a snapshot of the table used by the export test.
105    */
106   @Before
107   public void setUp() throws Exception {
108     this.admin = TEST_UTIL.getHBaseAdmin();
109 
110     long tid = System.currentTimeMillis();
111     tableName = TableName.valueOf("testtb-" + tid);
112     snapshotName = Bytes.toBytes("snaptb0-" + tid);
113     emptySnapshotName = Bytes.toBytes("emptySnaptb0-" + tid);
114 
115     // create Table
116     SnapshotTestingUtils.createTable(TEST_UTIL, tableName, FAMILY);
117 
118     // Take an empty snapshot
119     admin.snapshot(emptySnapshotName, tableName);
120 
121     // Add some rows
122     HTable table = new HTable(TEST_UTIL.getConfiguration(), tableName);
123     SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 500, FAMILY);
124 
125     // take a snapshot
126     admin.snapshot(snapshotName, tableName);
127   }
128 
129   @After
130   public void tearDown() throws Exception {
131     TEST_UTIL.deleteTable(tableName);
132     SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getHBaseAdmin());
133     SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
134   }
135 
136 
137   /**
138    * Verfy the result of getBalanceSplits() method.
139    * The result are groups of files, used as input list for the "export" mappers.
140    * All the groups should have similar amount of data.
141    *
142    * The input list is a pair of file path and length.
143    * The getBalanceSplits() function sort it by length,
144    * and assign to each group a file, going back and forth through the groups.
145    */
146   @Test
147   public void testBalanceSplit() throws Exception {
148     // Create a list of files
149     List<Pair<SnapshotFileInfo, Long>> files = new ArrayList<Pair<SnapshotFileInfo, Long>>();
150     for (long i = 0; i <= 20; i++) {
151       SnapshotFileInfo fileInfo = SnapshotFileInfo.newBuilder()
152         .setType(SnapshotFileInfo.Type.HFILE)
153         .setHfile("file-" + i)
154         .build();
155       files.add(new Pair<SnapshotFileInfo, Long>(fileInfo, i));
156     }
157 
158     // Create 5 groups (total size 210)
159     //    group 0: 20, 11, 10,  1 (total size: 42)
160     //    group 1: 19, 12,  9,  2 (total size: 42)
161     //    group 2: 18, 13,  8,  3 (total size: 42)
162     //    group 3: 17, 12,  7,  4 (total size: 42)
163     //    group 4: 16, 11,  6,  5 (total size: 42)
164     List<List<Pair<SnapshotFileInfo, Long>>> splits = ExportSnapshot.getBalancedSplits(files, 5);
165     assertEquals(5, splits.size());
166 
167     String[] split0 = new String[] {"file-20", "file-11", "file-10", "file-1", "file-0"};
168     verifyBalanceSplit(splits.get(0), split0, 42);
169     String[] split1 = new String[] {"file-19", "file-12", "file-9",  "file-2"};
170     verifyBalanceSplit(splits.get(1), split1, 42);
171     String[] split2 = new String[] {"file-18", "file-13", "file-8",  "file-3"};
172     verifyBalanceSplit(splits.get(2), split2, 42);
173     String[] split3 = new String[] {"file-17", "file-14", "file-7",  "file-4"};
174     verifyBalanceSplit(splits.get(3), split3, 42);
175     String[] split4 = new String[] {"file-16", "file-15", "file-6",  "file-5"};
176     verifyBalanceSplit(splits.get(4), split4, 42);
177   }
178 
179   private void verifyBalanceSplit(final List<Pair<SnapshotFileInfo, Long>> split,
180       final String[] expected, final long expectedSize) {
181     assertEquals(expected.length, split.size());
182     long totalSize = 0;
183     for (int i = 0; i < expected.length; ++i) {
184       Pair<SnapshotFileInfo, Long> fileInfo = split.get(i);
185       assertEquals(expected[i], fileInfo.getFirst().getHfile());
186       totalSize += fileInfo.getSecond();
187     }
188     assertEquals(expectedSize, totalSize);
189   }
190 
191   /**
192    * Verify if exported snapshot and copied files matches the original one.
193    */
194   @Test
195   public void testExportFileSystemState() throws Exception {
196     testExportFileSystemState(tableName, snapshotName, snapshotName, 2);
197   }
198 
199   @Test
200   public void testExportFileSystemStateWithSkipTmp() throws Exception {
201     TEST_UTIL.getConfiguration().setBoolean(ExportSnapshot.CONF_SKIP_TMP, true);
202     testExportFileSystemState(tableName, snapshotName, snapshotName, 2);
203   }
204 
205   @Test
206   public void testEmptyExportFileSystemState() throws Exception {
207     testExportFileSystemState(tableName, emptySnapshotName, emptySnapshotName, 1);
208   }
209 
210   @Test
211   public void testConsecutiveExports() throws Exception {
212     Path copyDir = getLocalDestinationDir();
213     testExportFileSystemState(tableName, snapshotName, snapshotName, 2, copyDir, false);
214     testExportFileSystemState(tableName, snapshotName, snapshotName, 2, copyDir, true);
215     removeExportDir(copyDir);
216   }
217 
218   @Test
219   public void testExportWithTargetName() throws Exception {
220     final byte[] targetName = Bytes.toBytes("testExportWithTargetName");
221     testExportFileSystemState(tableName, snapshotName, targetName, 2);
222   }
223 
224   /**
225    * Mock a snapshot with files in the archive dir,
226    * two regions, and one reference file.
227    */
228   @Test
229   public void testSnapshotWithRefsExportFileSystemState() throws Exception {
230     Configuration conf = TEST_UTIL.getConfiguration();
231 
232     final TableName tableWithRefsName =
233         TableName.valueOf("tableWithRefs");
234     final String snapshotName = "tableWithRefs";
235     final String TEST_FAMILY = Bytes.toString(FAMILY);
236     final String TEST_HFILE = "abc";
237 
238     final SnapshotDescription sd = SnapshotDescription.newBuilder()
239         .setName(snapshotName)
240         .setTable(tableWithRefsName.getNameAsString()).build();
241 
242     FileSystem fs = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
243     Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
244     Path archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
245 
246     // First region, simple with one plain hfile.
247     HRegionInfo hri = new HRegionInfo(tableWithRefsName);
248     HRegionFileSystem r0fs = HRegionFileSystem.createRegionOnFileSystem(conf,
249       fs, FSUtils.getTableDir(archiveDir, hri.getTable()), hri);
250     Path storeFile = new Path(rootDir, TEST_HFILE);
251     FSDataOutputStream out = fs.create(storeFile);
252     out.write(Bytes.toBytes("Test Data"));
253     out.close();
254     r0fs.commitStoreFile(TEST_FAMILY, storeFile);
255 
256     // Second region, used to test the split case.
257     // This region contains a reference to the hfile in the first region.
258     hri = new HRegionInfo(tableWithRefsName);
259     HRegionFileSystem r1fs = HRegionFileSystem.createRegionOnFileSystem(conf,
260       fs, new Path(archiveDir, hri.getTable().getNameAsString()), hri);
261     storeFile = new Path(rootDir, TEST_HFILE + '.' + r0fs.getRegionInfo().getEncodedName());
262     out = fs.create(storeFile);
263     out.write(Bytes.toBytes("Test Data"));
264     out.close();
265     r1fs.commitStoreFile(TEST_FAMILY, storeFile);
266 
267     Path tableDir = FSUtils.getTableDir(archiveDir, tableWithRefsName);
268     HTableDescriptor htd = new HTableDescriptor(tableWithRefsName);
269     htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
270     new FSTableDescriptors(conf, fs, rootDir)
271         .createTableDescriptorForTableDirectory(tableDir, htd, false);
272 
273     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
274     FileUtil.copy(fs, tableDir, fs, snapshotDir, false, conf);
275     SnapshotDescriptionUtils.writeSnapshotInfo(sd, snapshotDir, fs);
276 
277     byte[] name = Bytes.toBytes(snapshotName);
278     testExportFileSystemState(tableWithRefsName, name, name, 2);
279   }
280 
281   private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName,
282       final byte[] targetName, int filesExpected) throws Exception {
283     Path copyDir = getHdfsDestinationDir();
284     testExportFileSystemState(tableName, snapshotName, targetName, filesExpected, copyDir, false);
285     removeExportDir(copyDir);
286   }
287 
288   /**
289    * Test ExportSnapshot
290    */
291   private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName,
292       final byte[] targetName, int filesExpected, Path copyDir, boolean overwrite)
293       throws Exception {
294     URI hdfsUri = FileSystem.get(TEST_UTIL.getConfiguration()).getUri();
295     FileSystem fs = FileSystem.get(copyDir.toUri(), new Configuration());
296     copyDir = copyDir.makeQualified(fs);
297 
298     List<String> opts = new ArrayList<String>();
299     opts.add("-snapshot");
300     opts.add(Bytes.toString(snapshotName));
301     opts.add("-copy-to");
302     opts.add(copyDir.toString());
303     if (targetName != snapshotName) {
304       opts.add("-target");
305       opts.add(Bytes.toString(targetName));
306     }
307     if (overwrite) opts.add("-overwrite");
308 
309     // Export Snapshot
310     int res = ExportSnapshot.innerMain(TEST_UTIL.getConfiguration(),
311         opts.toArray(new String[opts.size()]));
312     assertEquals(0, res);
313 
314     // Verify File-System state
315     FileStatus[] rootFiles = fs.listStatus(copyDir);
316     assertEquals(filesExpected, rootFiles.length);
317     for (FileStatus fileStatus: rootFiles) {
318       String name = fileStatus.getPath().getName();
319       assertTrue(fileStatus.isDir());
320       assertTrue(name.equals(HConstants.SNAPSHOT_DIR_NAME) ||
321                  name.equals(HConstants.HFILE_ARCHIVE_DIRECTORY));
322     }
323 
324     // compare the snapshot metadata and verify the hfiles
325     final FileSystem hdfs = FileSystem.get(hdfsUri, TEST_UTIL.getConfiguration());
326     final Path snapshotDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(snapshotName));
327     final Path targetDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(targetName));
328     verifySnapshot(hdfs, new Path(TEST_UTIL.getDefaultRootDirPath(), snapshotDir),
329         fs, new Path(copyDir, targetDir));
330     verifyArchive(fs, copyDir, tableName, Bytes.toString(targetName));
331     FSUtils.logFileSystemState(hdfs, snapshotDir, LOG);
332   }
333 
334   /**
335    * Check that ExportSnapshot will return a failure if something fails.
336    */
337   @Test
338   public void testExportFailure() throws Exception {
339     assertEquals(1, runExportAndInjectFailures(snapshotName, false));
340   }
341 
342   /**
343    * Check that ExportSnapshot will succede if something fails but the retry succede.
344    */
345   @Test
346   public void testExportRetry() throws Exception {
347     assertEquals(0, runExportAndInjectFailures(snapshotName, true));
348   }
349 
350   /*
351    * Execute the ExportSnapshot job injecting failures
352    */
353   private int runExportAndInjectFailures(final byte[] snapshotName, boolean retry)
354       throws Exception {
355     Path copyDir = getLocalDestinationDir();
356     URI hdfsUri = FileSystem.get(TEST_UTIL.getConfiguration()).getUri();
357     FileSystem fs = FileSystem.get(copyDir.toUri(), new Configuration());
358     copyDir = copyDir.makeQualified(fs);
359 
360     Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
361     conf.setBoolean(ExportSnapshot.CONF_TEST_FAILURE, true);
362     conf.setBoolean(ExportSnapshot.CONF_TEST_RETRY, retry);
363 
364     // Export Snapshot
365     Path sourceDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
366     int res = ExportSnapshot.innerMain(conf, new String[] {
367       "-snapshot", Bytes.toString(snapshotName),
368       "-copy-from", sourceDir.toString(),
369       "-copy-to", copyDir.toString()
370     });
371     return res;
372   }
373 
374   /*
375    * verify if the snapshot folder on file-system 1 match the one on file-system 2
376    */
377   private void verifySnapshot(final FileSystem fs1, final Path root1,
378       final FileSystem fs2, final Path root2) throws IOException {
379     Set<String> s = new HashSet<String>();
380     assertEquals(listFiles(fs1, root1, root1), listFiles(fs2, root2, root2));
381   }
382 
383   /*
384    * Verify if the files exists
385    */
386   private void verifyArchive(final FileSystem fs, final Path rootDir,
387       final TableName tableName, final String snapshotName) throws IOException {
388     final Path exportedSnapshot = new Path(rootDir,
389       new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName));
390     final Path exportedArchive = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
391     LOG.debug(listFiles(fs, exportedArchive, exportedArchive));
392     SnapshotReferenceUtil.visitReferencedFiles(TEST_UTIL.getConfiguration(), fs, exportedSnapshot,
393           new SnapshotReferenceUtil.SnapshotVisitor() {
394         public void storeFile(final HRegionInfo regionInfo, final String family,
395             final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
396           String hfile = storeFile.getName();
397           verifyNonEmptyFile(new Path(exportedArchive,
398             new Path(FSUtils.getTableDir(new Path("./"), tableName),
399                 new Path(regionInfo.getEncodedName(), new Path(family, hfile)))));
400         }
401 
402         public void recoveredEdits (final String region, final String logfile)
403             throws IOException {
404           verifyNonEmptyFile(new Path(exportedSnapshot,
405             new Path(tableName.getNameAsString(), new Path(region, logfile))));
406         }
407 
408         public void logFile (final String server, final String logfile)
409             throws IOException {
410           verifyNonEmptyFile(new Path(exportedSnapshot, new Path(server, logfile)));
411         }
412 
413         private void verifyNonEmptyFile(final Path path) throws IOException {
414           assertTrue(path + " should exists", fs.exists(path));
415           assertTrue(path + " should not be empty", fs.getFileStatus(path).getLen() > 0);
416         }
417     });
418 
419     // Verify Snapshot description
420     SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, exportedSnapshot);
421     assertTrue(desc.getName().equals(snapshotName));
422     assertTrue(desc.getTable().equals(tableName.getNameAsString()));
423   }
424 
425   private Set<String> listFiles(final FileSystem fs, final Path root, final Path dir)
426       throws IOException {
427     Set<String> files = new HashSet<String>();
428     int rootPrefix = root.toString().length();
429     FileStatus[] list = FSUtils.listStatus(fs, dir);
430     if (list != null) {
431       for (FileStatus fstat: list) {
432         LOG.debug(fstat.getPath());
433         if (fstat.isDir()) {
434           files.addAll(listFiles(fs, root, fstat.getPath()));
435         } else {
436           files.add(fstat.getPath().toString().substring(rootPrefix));
437         }
438       }
439     }
440     return files;
441   }
442 
443   private Path getHdfsDestinationDir() {
444     Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
445     Path path = new Path(new Path(rootDir, "export-test"), "export-" + System.currentTimeMillis());
446     LOG.info("HDFS export destination path: " + path);
447     return path;
448   }
449 
450   private Path getLocalDestinationDir() {
451     Path path = TEST_UTIL.getDataTestDir("local-export-" + System.currentTimeMillis());
452     LOG.info("Local export destination path: " + path);
453     return path;
454   }
455 
456   private void removeExportDir(final Path path) throws IOException {
457     FileSystem fs = FileSystem.get(path.toUri(), new Configuration());
458     FSUtils.logFileSystemState(fs, path, LOG);
459     fs.delete(path, true);
460   }
461 }