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    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    }
85  
86    @BeforeClass
87    public static void setUpBeforeClass() throws Exception {
88      setUpBaseConf(TEST_UTIL.getConfiguration());
89      TEST_UTIL.startMiniCluster(3);
90      TEST_UTIL.startMiniMapReduceCluster();
91    }
92  
93    @AfterClass
94    public static void tearDownAfterClass() throws Exception {
95      TEST_UTIL.shutdownMiniMapReduceCluster();
96      TEST_UTIL.shutdownMiniCluster();
97    }
98  
99    /**
100    * Create a table and take a snapshot of the table used by the export test.
101    */
102   @Before
103   public void setUp() throws Exception {
104     this.admin = TEST_UTIL.getHBaseAdmin();
105 
106     long tid = System.currentTimeMillis();
107     tableName = TableName.valueOf("testtb-" + tid);
108     snapshotName = Bytes.toBytes("snaptb0-" + tid);
109     emptySnapshotName = Bytes.toBytes("emptySnaptb0-" + tid);
110 
111     // create Table
112     SnapshotTestingUtils.createTable(TEST_UTIL, tableName, FAMILY);
113 
114     // Take an empty snapshot
115     admin.snapshot(emptySnapshotName, tableName);
116 
117     // Add some rows
118     Table table = new HTable(TEST_UTIL.getConfiguration(), tableName);
119     SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY);
120     tableNumFiles = admin.getTableRegions(tableName).size();
121 
122     // take a snapshot
123     admin.snapshot(snapshotName, tableName);
124   }
125 
126   @After
127   public void tearDown() throws Exception {
128     TEST_UTIL.deleteTable(tableName);
129     SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getHBaseAdmin());
130     SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
131   }
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 = snapshotMock.createSnapshotV2("tableWithRefsV1");
234     testSnapshotWithRefsExportFileSystemState(builder);
235 
236     snapshotMock = new SnapshotMock(TEST_UTIL.getConfiguration(), fs, rootDir);
237     builder = snapshotMock.createSnapshotV2("tableWithRefsV2");
238     testSnapshotWithRefsExportFileSystemState(builder);
239   }
240 
241   /**
242    * Generates a couple of regions for the specified SnapshotMock,
243    * and then it will run the export and verification.
244    */
245   private void testSnapshotWithRefsExportFileSystemState(SnapshotMock.SnapshotBuilder builder)
246       throws Exception {
247     Path[] r1Files = builder.addRegion();
248     Path[] r2Files = builder.addRegion();
249     builder.commit();
250     int snapshotFilesCount = r1Files.length + r2Files.length;
251 
252     byte[] snapshotName = Bytes.toBytes(builder.getSnapshotDescription().getName());
253     TableName tableName = builder.getTableDescriptor().getTableName();
254     testExportFileSystemState(tableName, snapshotName, snapshotName, snapshotFilesCount);
255   }
256 
257   private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName,
258       final byte[] targetName, int filesExpected) throws Exception {
259     Path copyDir = getHdfsDestinationDir();
260     testExportFileSystemState(tableName, snapshotName, targetName, filesExpected, copyDir, false);
261     removeExportDir(copyDir);
262   }
263 
264   /**
265    * Test ExportSnapshot
266    */
267   private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName,
268       final byte[] targetName, int filesExpected, Path copyDir, boolean overwrite)
269       throws Exception {
270     URI hdfsUri = FileSystem.get(TEST_UTIL.getConfiguration()).getUri();
271     FileSystem fs = FileSystem.get(copyDir.toUri(), new Configuration());
272     copyDir = copyDir.makeQualified(fs);
273 
274     List<String> opts = new ArrayList<String>();
275     opts.add("-snapshot");
276     opts.add(Bytes.toString(snapshotName));
277     opts.add("-copy-to");
278     opts.add(copyDir.toString());
279     if (targetName != snapshotName) {
280       opts.add("-target");
281       opts.add(Bytes.toString(targetName));
282     }
283     if (overwrite) opts.add("-overwrite");
284 
285     // Export Snapshot
286     int res = ExportSnapshot.innerMain(TEST_UTIL.getConfiguration(),
287         opts.toArray(new String[opts.size()]));
288     assertEquals(0, res);
289 
290     // Verify File-System state
291     FileStatus[] rootFiles = fs.listStatus(copyDir);
292     assertEquals(filesExpected > 0 ? 2 : 1, rootFiles.length);
293     for (FileStatus fileStatus: rootFiles) {
294       String name = fileStatus.getPath().getName();
295       assertTrue(fileStatus.isDirectory());
296       assertTrue(name.equals(HConstants.SNAPSHOT_DIR_NAME) ||
297                  name.equals(HConstants.HFILE_ARCHIVE_DIRECTORY));
298     }
299 
300     // compare the snapshot metadata and verify the hfiles
301     final FileSystem hdfs = FileSystem.get(hdfsUri, TEST_UTIL.getConfiguration());
302     final Path snapshotDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(snapshotName));
303     final Path targetDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(targetName));
304     verifySnapshotDir(hdfs, new Path(TEST_UTIL.getDefaultRootDirPath(), snapshotDir),
305         fs, new Path(copyDir, targetDir));
306     Set<String> snapshotFiles = verifySnapshot(fs, copyDir, tableName, Bytes.toString(targetName));
307     assertEquals(filesExpected, snapshotFiles.size());
308   }
309 
310   /**
311    * Check that ExportSnapshot will return a failure if something fails.
312    */
313   @Test
314   public void testExportFailure() throws Exception {
315     assertEquals(1, runExportAndInjectFailures(snapshotName, false));
316   }
317 
318   /**
319    * Check that ExportSnapshot will succede if something fails but the retry succede.
320    */
321   @Test
322   public void testExportRetry() throws Exception {
323     assertEquals(0, runExportAndInjectFailures(snapshotName, true));
324   }
325 
326   /*
327    * Execute the ExportSnapshot job injecting failures
328    */
329   private int runExportAndInjectFailures(final byte[] snapshotName, boolean retry)
330       throws Exception {
331     Path copyDir = getLocalDestinationDir();
332     URI hdfsUri = FileSystem.get(TEST_UTIL.getConfiguration()).getUri();
333     FileSystem fs = FileSystem.get(copyDir.toUri(), new Configuration());
334     copyDir = copyDir.makeQualified(fs);
335 
336     Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
337     conf.setBoolean(ExportSnapshot.CONF_TEST_FAILURE, true);
338     conf.setBoolean(ExportSnapshot.CONF_TEST_RETRY, retry);
339 
340     // Export Snapshot
341     Path sourceDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
342     int res = ExportSnapshot.innerMain(conf, new String[] {
343       "-snapshot", Bytes.toString(snapshotName),
344       "-copy-from", sourceDir.toString(),
345       "-copy-to", copyDir.toString()
346     });
347     return res;
348   }
349 
350   /*
351    * verify if the snapshot folder on file-system 1 match the one on file-system 2
352    */
353   private void verifySnapshotDir(final FileSystem fs1, final Path root1,
354       final FileSystem fs2, final Path root2) throws IOException {
355     assertEquals(listFiles(fs1, root1, root1), listFiles(fs2, root2, root2));
356   }
357 
358   /*
359    * Verify if the files exists
360    */
361   private Set<String> verifySnapshot(final FileSystem fs, final Path rootDir,
362       final TableName tableName, final String snapshotName) throws IOException {
363     final Path exportedSnapshot = new Path(rootDir,
364       new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName));
365     final Set<String> snapshotFiles = new HashSet<String>();
366     final Path exportedArchive = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
367     SnapshotReferenceUtil.visitReferencedFiles(TEST_UTIL.getConfiguration(), fs, exportedSnapshot,
368           new SnapshotReferenceUtil.SnapshotVisitor() {
369         @Override
370         public void storeFile(final HRegionInfo regionInfo, final String family,
371             final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
372           String hfile = storeFile.getName();
373           snapshotFiles.add(hfile);
374           if (storeFile.hasReference()) {
375             // Nothing to do here, we have already the reference embedded
376           } else {
377             verifyNonEmptyFile(new Path(exportedArchive,
378               new Path(FSUtils.getTableDir(new Path("./"), tableName),
379                   new Path(regionInfo.getEncodedName(), new Path(family, hfile)))));
380           }
381         }
382 
383         @Override
384         public void logFile (final String server, final String logfile)
385             throws IOException {
386           snapshotFiles.add(logfile);
387           verifyNonEmptyFile(new Path(exportedSnapshot, new Path(server, logfile)));
388         }
389 
390         private void verifyNonEmptyFile(final Path path) throws IOException {
391           assertTrue(path + " should exists", fs.exists(path));
392           assertTrue(path + " should not be empty", fs.getFileStatus(path).getLen() > 0);
393         }
394     });
395 
396     // Verify Snapshot description
397     SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, exportedSnapshot);
398     assertTrue(desc.getName().equals(snapshotName));
399     assertTrue(desc.getTable().equals(tableName.getNameAsString()));
400     return snapshotFiles;
401   }
402 
403   private Set<String> listFiles(final FileSystem fs, final Path root, final Path dir)
404       throws IOException {
405     Set<String> files = new HashSet<String>();
406     int rootPrefix = root.toString().length();
407     FileStatus[] list = FSUtils.listStatus(fs, dir);
408     if (list != null) {
409       for (FileStatus fstat: list) {
410         LOG.debug(fstat.getPath());
411         if (fstat.isDirectory()) {
412           files.addAll(listFiles(fs, root, fstat.getPath()));
413         } else {
414           files.add(fstat.getPath().toString().substring(rootPrefix));
415         }
416       }
417     }
418     return files;
419   }
420 
421   private Path getHdfsDestinationDir() {
422     Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
423     Path path = new Path(new Path(rootDir, "export-test"), "export-" + System.currentTimeMillis());
424     LOG.info("HDFS export destination path: " + path);
425     return path;
426   }
427 
428   private Path getLocalDestinationDir() {
429     Path path = TEST_UTIL.getDataTestDir("local-export-" + System.currentTimeMillis());
430     LOG.info("Local export destination path: " + path);
431     return path;
432   }
433 
434   private void removeExportDir(final Path path) throws IOException {
435     FileSystem fs = FileSystem.get(path.toUri(), new Configuration());
436     fs.delete(path, true);
437   }
438 }