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