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