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