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