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.CategoryBasedTimeout;
38  import org.apache.hadoop.hbase.HBaseTestingUtility;
39  import org.apache.hadoop.hbase.HConstants;
40  import org.apache.hadoop.hbase.HRegionInfo;
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.testclassification.MediumTests;
51  import org.apache.hadoop.hbase.util.Bytes;
52  import org.apache.hadoop.hbase.util.FSUtils;
53  import org.apache.hadoop.hbase.util.Pair;
54  import org.junit.After;
55  import org.junit.AfterClass;
56  import org.junit.Before;
57  import org.junit.BeforeClass;
58  import org.junit.Rule;
59  import org.junit.Test;
60  import org.junit.experimental.categories.Category;
61  import org.junit.rules.TestRule;
62  
63  /**
64   * Test Export Snapshot Tool
65   */
66  @Category(MediumTests.class)
67  public class TestExportSnapshot {
68    @Rule public final TestRule timeout = CategoryBasedTimeout.builder().
69        withTimeout(this.getClass()).withLookingForStuckThread(true).build();
70    private static final Log LOG = LogFactory.getLog(TestExportSnapshot.class);
71  
72    protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
73  
74    private final static byte[] FAMILY = Bytes.toBytes("cf");
75  
76    private byte[] emptySnapshotName;
77    private byte[] snapshotName;
78    private int tableNumFiles;
79    private TableName tableName;
80    private Admin admin;
81  
82    public static void setUpBaseConf(Configuration conf) {
83      conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
84      conf.setInt("hbase.regionserver.msginterval", 100);
85      conf.setInt("hbase.client.pause", 250);
86      conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
87      conf.setBoolean("hbase.master.enabletable.roundrobin", true);
88      conf.setInt("mapreduce.map.maxattempts", 10);
89    }
90  
91    @BeforeClass
92    public static void setUpBeforeClass() throws Exception {
93      setUpBaseConf(TEST_UTIL.getConfiguration());
94      TEST_UTIL.startMiniCluster(3);
95      TEST_UTIL.startMiniMapReduceCluster();
96    }
97  
98    @AfterClass
99    public static void tearDownAfterClass() throws Exception {
100     TEST_UTIL.shutdownMiniMapReduceCluster();
101     TEST_UTIL.shutdownMiniCluster();
102   }
103 
104   /**
105    * Create a table and take a snapshot of the table used by the export test.
106    */
107   @Before
108   public void setUp() throws Exception {
109     this.admin = TEST_UTIL.getHBaseAdmin();
110 
111     long tid = System.currentTimeMillis();
112     tableName = TableName.valueOf("testtb-" + tid);
113     snapshotName = Bytes.toBytes("snaptb0-" + tid);
114     emptySnapshotName = Bytes.toBytes("emptySnaptb0-" + tid);
115 
116     // create Table
117     SnapshotTestingUtils.createTable(TEST_UTIL, tableName, FAMILY);
118 
119     // Take an empty snapshot
120     admin.snapshot(emptySnapshotName, tableName);
121 
122     // Add some rows
123     Table table = new HTable(TEST_UTIL.getConfiguration(), tableName);
124     SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY);
125     tableNumFiles = admin.getTableRegions(tableName).size();
126 
127     // take a snapshot
128     admin.snapshot(snapshotName, tableName);
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   /*
365    * Verify if the files exists
366    */
367   private Set<String> verifySnapshot(final FileSystem fs, final Path rootDir,
368       final TableName tableName, final String snapshotName) throws IOException {
369     final Path exportedSnapshot = new Path(rootDir,
370       new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName));
371     final Set<String> snapshotFiles = new HashSet<String>();
372     final Path exportedArchive = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
373     SnapshotReferenceUtil.visitReferencedFiles(TEST_UTIL.getConfiguration(), fs, exportedSnapshot,
374           new SnapshotReferenceUtil.SnapshotVisitor() {
375         @Override
376         public void storeFile(final HRegionInfo regionInfo, final String family,
377             final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
378           String hfile = storeFile.getName();
379           snapshotFiles.add(hfile);
380           if (storeFile.hasReference()) {
381             // Nothing to do here, we have already the reference embedded
382           } else {
383             verifyNonEmptyFile(new Path(exportedArchive,
384               new Path(FSUtils.getTableDir(new Path("./"), tableName),
385                   new Path(regionInfo.getEncodedName(), new Path(family, hfile)))));
386           }
387         }
388 
389         @Override
390         public void logFile (final String server, final String logfile)
391             throws IOException {
392           snapshotFiles.add(logfile);
393           verifyNonEmptyFile(new Path(exportedSnapshot, new Path(server, logfile)));
394         }
395 
396         private void verifyNonEmptyFile(final Path path) throws IOException {
397           assertTrue(path + " should exists", fs.exists(path));
398           assertTrue(path + " should not be empty", fs.getFileStatus(path).getLen() > 0);
399         }
400     });
401 
402     // Verify Snapshot description
403     SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, exportedSnapshot);
404     assertTrue(desc.getName().equals(snapshotName));
405     assertTrue(desc.getTable().equals(tableName.getNameAsString()));
406     return snapshotFiles;
407   }
408 
409   private Set<String> listFiles(final FileSystem fs, final Path root, final Path dir)
410       throws IOException {
411     Set<String> files = new HashSet<String>();
412     int rootPrefix = root.toString().length();
413     FileStatus[] list = FSUtils.listStatus(fs, dir);
414     if (list != null) {
415       for (FileStatus fstat: list) {
416         LOG.debug(fstat.getPath());
417         if (fstat.isDirectory()) {
418           files.addAll(listFiles(fs, root, fstat.getPath()));
419         } else {
420           files.add(fstat.getPath().toString().substring(rootPrefix));
421         }
422       }
423     }
424     return files;
425   }
426 
427   private Path getHdfsDestinationDir() {
428     Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
429     Path path = new Path(new Path(rootDir, "export-test"), "export-" + System.currentTimeMillis());
430     LOG.info("HDFS export destination path: " + path);
431     return path;
432   }
433 
434   private Path getLocalDestinationDir() {
435     Path path = TEST_UTIL.getDataTestDir("local-export-" + System.currentTimeMillis());
436     LOG.info("Local export destination path: " + path);
437     return path;
438   }
439 
440   private void removeExportDir(final Path path) throws IOException {
441     FileSystem fs = FileSystem.get(path.toUri(), new Configuration());
442     fs.delete(path, true);
443   }
444 }