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.Arrays;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.fs.FSDataOutputStream;
36  import org.apache.hadoop.fs.FileStatus;
37  import org.apache.hadoop.fs.FileSystem;
38  import org.apache.hadoop.fs.FileUtil;
39  import org.apache.hadoop.fs.Path;
40  import org.apache.hadoop.hbase.HBaseTestingUtility;
41  import org.apache.hadoop.hbase.HColumnDescriptor;
42  import org.apache.hadoop.hbase.HConstants;
43  import org.apache.hadoop.hbase.HRegionInfo;
44  import org.apache.hadoop.hbase.HTableDescriptor;
45  import org.apache.hadoop.hbase.MediumTests;
46  import org.apache.hadoop.hbase.TableName;
47  import org.apache.hadoop.hbase.client.HBaseAdmin;
48  import org.apache.hadoop.hbase.client.HTable;
49  import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
50  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
51  import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
52  import org.apache.hadoop.hbase.util.Bytes;
53  import org.apache.hadoop.hbase.util.FSUtils;
54  import org.apache.hadoop.hbase.util.Pair;
55  import org.junit.After;
56  import org.junit.AfterClass;
57  import org.junit.Before;
58  import org.junit.BeforeClass;
59  import org.junit.Test;
60  import org.junit.experimental.categories.Category;
61  
62  /**
63   * Test Export Snapshot Tool
64   */
65  @Category(MediumTests.class)
66  public class TestExportSnapshot {
67    private final Log LOG = LogFactory.getLog(getClass());
68  
69    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
70  
71    private final static byte[] FAMILY = Bytes.toBytes("cf");
72  
73    private byte[] emptySnapshotName;
74    private byte[] snapshotName;
75    private TableName tableName;
76    private HBaseAdmin admin;
77  
78    @BeforeClass
79    public static void setUpBeforeClass() throws Exception {
80      TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
81      TEST_UTIL.getConfiguration().setInt("hbase.regionserver.msginterval", 100);
82      TEST_UTIL.getConfiguration().setInt("hbase.client.pause", 250);
83      TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
84      TEST_UTIL.getConfiguration().setBoolean("hbase.master.enabletable.roundrobin", true);
85      TEST_UTIL.startMiniCluster(3);
86      TEST_UTIL.startMiniMapReduceCluster();
87    }
88  
89    @AfterClass
90    public static void tearDownAfterClass() throws Exception {
91      TEST_UTIL.shutdownMiniMapReduceCluster();
92      TEST_UTIL.shutdownMiniCluster();
93    }
94  
95    /**
96     * Create a table and take a snapshot of the table used by the export test.
97     */
98    @Before
99    public void setUp() throws Exception {
100     this.admin = TEST_UTIL.getHBaseAdmin();
101 
102     long tid = System.currentTimeMillis();
103     tableName = TableName.valueOf("testtb-" + tid);
104     snapshotName = Bytes.toBytes("snaptb0-" + tid);
105     emptySnapshotName = Bytes.toBytes("emptySnaptb0-" + tid);
106 
107     // create Table
108     HTableDescriptor htd = new HTableDescriptor(tableName);
109     htd.addFamily(new HColumnDescriptor(FAMILY));
110     admin.createTable(htd, null);
111 
112     // Take an empty snapshot
113     admin.snapshot(emptySnapshotName, tableName);
114 
115     // Add some rows
116     HTable table = new HTable(TEST_UTIL.getConfiguration(), tableName);
117     TEST_UTIL.loadTable(table, FAMILY);
118 
119     // take a snapshot
120     admin.snapshot(snapshotName, tableName);
121   }
122 
123   @After
124   public void tearDown() throws Exception {
125     TEST_UTIL.deleteTable(tableName);
126     SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getHBaseAdmin());
127     SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
128   }
129 
130   /**
131    * Verfy the result of getBalanceSplits() method.
132    * The result are groups of files, used as input list for the "export" mappers.
133    * All the groups should have similar amount of data.
134    *
135    * The input list is a pair of file path and length.
136    * The getBalanceSplits() function sort it by length,
137    * and assign to each group a file, going back and forth through the groups.
138    */
139   @Test
140   public void testBalanceSplit() throws Exception {
141     // Create a list of files
142     List<Pair<Path, Long>> files = new ArrayList<Pair<Path, Long>>();
143     for (long i = 0; i <= 20; i++) {
144       files.add(new Pair<Path, Long>(new Path("file-" + i), i));
145     }
146 
147     // Create 5 groups (total size 210)
148     //    group 0: 20, 11, 10,  1 (total size: 42)
149     //    group 1: 19, 12,  9,  2 (total size: 42)
150     //    group 2: 18, 13,  8,  3 (total size: 42)
151     //    group 3: 17, 12,  7,  4 (total size: 42)
152     //    group 4: 16, 11,  6,  5 (total size: 42)
153     List<List<Path>> splits = ExportSnapshot.getBalancedSplits(files, 5);
154     assertEquals(5, splits.size());
155     assertEquals(Arrays.asList(new Path("file-20"), new Path("file-11"),
156       new Path("file-10"), new Path("file-1"), new Path("file-0")), splits.get(0));
157     assertEquals(Arrays.asList(new Path("file-19"), new Path("file-12"),
158       new Path("file-9"), new Path("file-2")), splits.get(1));
159     assertEquals(Arrays.asList(new Path("file-18"), new Path("file-13"),
160       new Path("file-8"), new Path("file-3")), splits.get(2));
161     assertEquals(Arrays.asList(new Path("file-17"), new Path("file-14"),
162       new Path("file-7"), new Path("file-4")), splits.get(3));
163     assertEquals(Arrays.asList(new Path("file-16"), new Path("file-15"),
164       new Path("file-6"), new Path("file-5")), splits.get(4));
165   }
166 
167   /**
168    * Verify if exported snapshot and copied files matches the original one.
169    */
170   @Test
171   public void testExportFileSystemState() throws Exception {
172     testExportFileSystemState(tableName, snapshotName, 2);
173   }
174 
175   @Test
176   public void testEmptyExportFileSystemState() throws Exception {
177     testExportFileSystemState(tableName, emptySnapshotName, 1);
178   }
179 
180   /**
181    * Mock a snapshot with files in the archive dir,
182    * two regions, and one reference file.
183    */
184   @Test
185   public void testSnapshotWithRefsExportFileSystemState() throws Exception {
186     Configuration conf = TEST_UTIL.getConfiguration();
187 
188     final TableName tableWithRefsName =
189         TableName.valueOf("tableWithRefs");
190     final String snapshotName = "tableWithRefs";
191     final String TEST_FAMILY = Bytes.toString(FAMILY);
192     final String TEST_HFILE = "abc";
193 
194     final SnapshotDescription sd = SnapshotDescription.newBuilder()
195         .setName(snapshotName)
196         .setTable(tableWithRefsName.getNameAsString()).build();
197 
198     FileSystem fs = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
199     Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
200     Path archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
201 
202     // First region, simple with one plain hfile.
203     HRegionInfo hri = new HRegionInfo(tableWithRefsName);
204     HRegionFileSystem r0fs = HRegionFileSystem.createRegionOnFileSystem(conf,
205       fs, FSUtils.getTableDir(archiveDir, hri.getTable()), hri);
206     Path storeFile = new Path(rootDir, TEST_HFILE);
207     FSDataOutputStream out = fs.create(storeFile);
208     out.write(Bytes.toBytes("Test Data"));
209     out.close();
210     r0fs.commitStoreFile(TEST_FAMILY, storeFile);
211 
212     // Second region, used to test the split case.
213     // This region contains a reference to the hfile in the first region.
214     hri = new HRegionInfo(tableWithRefsName);
215     HRegionFileSystem r1fs = HRegionFileSystem.createRegionOnFileSystem(conf,
216       fs, new Path(archiveDir, hri.getTable().getNameAsString()), hri);
217     storeFile = new Path(rootDir, TEST_HFILE + '.' + r0fs.getRegionInfo().getEncodedName());
218     out = fs.create(storeFile);
219     out.write(Bytes.toBytes("Test Data"));
220     out.close();
221     r1fs.commitStoreFile(TEST_FAMILY, storeFile);
222 
223     Path tableDir = FSUtils.getTableDir(archiveDir, tableWithRefsName);
224     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
225     FileUtil.copy(fs, tableDir, fs, snapshotDir, false, conf);
226     SnapshotDescriptionUtils.writeSnapshotInfo(sd, snapshotDir, fs);
227 
228     testExportFileSystemState(tableWithRefsName, Bytes.toBytes(snapshotName), 2);
229   }
230 
231   /**
232    * Test ExportSnapshot
233    */
234   private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName,
235       int filesExpected) throws Exception {
236     Path copyDir = TEST_UTIL.getDataTestDir("export-" + System.currentTimeMillis());
237     URI hdfsUri = FileSystem.get(TEST_UTIL.getConfiguration()).getUri();
238     FileSystem fs = FileSystem.get(copyDir.toUri(), new Configuration());
239     copyDir = copyDir.makeQualified(fs);
240 
241     // Export Snapshot
242     int res = ExportSnapshot.innerMain(TEST_UTIL.getConfiguration(), new String[] {
243       "-snapshot", Bytes.toString(snapshotName),
244       "-copy-to", copyDir.toString()
245     });
246     assertEquals(0, res);
247 
248     // Verify File-System state
249     FileStatus[] rootFiles = fs.listStatus(copyDir);
250     assertEquals(filesExpected, rootFiles.length);
251     for (FileStatus fileStatus: rootFiles) {
252       String name = fileStatus.getPath().getName();
253       assertTrue(fileStatus.isDir());
254       assertTrue(name.equals(HConstants.SNAPSHOT_DIR_NAME) ||
255                  name.equals(HConstants.HFILE_ARCHIVE_DIRECTORY));
256     }
257 
258     // compare the snapshot metadata and verify the hfiles
259     final FileSystem hdfs = FileSystem.get(hdfsUri, TEST_UTIL.getConfiguration());
260     final Path snapshotDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(snapshotName));
261     verifySnapshot(hdfs, new Path(TEST_UTIL.getDefaultRootDirPath(), snapshotDir),
262         fs, new Path(copyDir, snapshotDir));
263     verifyArchive(fs, copyDir, tableName, Bytes.toString(snapshotName));
264     FSUtils.logFileSystemState(hdfs, snapshotDir, LOG);
265 
266     // Remove the exported dir
267     fs.delete(copyDir, true);
268   }
269 
270   /*
271    * verify if the snapshot folder on file-system 1 match the one on file-system 2
272    */
273   private void verifySnapshot(final FileSystem fs1, final Path root1,
274       final FileSystem fs2, final Path root2) throws IOException {
275     Set<String> s = new HashSet<String>();
276     assertEquals(listFiles(fs1, root1, root1), listFiles(fs2, root2, root2));
277   }
278 
279   /*
280    * Verify if the files exists
281    */
282   private void verifyArchive(final FileSystem fs, final Path rootDir,
283       final TableName tableName, final String snapshotName) throws IOException {
284     final Path exportedSnapshot = new Path(rootDir,
285       new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName));
286     final Path exportedArchive = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
287     LOG.debug(listFiles(fs, exportedArchive, exportedArchive));
288     SnapshotReferenceUtil.visitReferencedFiles(fs, exportedSnapshot,
289         new SnapshotReferenceUtil.FileVisitor() {
290         public void storeFile (final String region, final String family, final String hfile)
291             throws IOException {
292           verifyNonEmptyFile(new Path(exportedArchive,
293             new Path(FSUtils.getTableDir(new Path("./"), tableName),
294                 new Path(region, new Path(family, hfile)))));
295         }
296 
297         public void recoveredEdits (final String region, final String logfile)
298             throws IOException {
299           verifyNonEmptyFile(new Path(exportedSnapshot,
300             new Path(tableName.getNameAsString(), new Path(region, logfile))));
301         }
302 
303         public void logFile (final String server, final String logfile)
304             throws IOException {
305           verifyNonEmptyFile(new Path(exportedSnapshot, new Path(server, logfile)));
306         }
307 
308         private void verifyNonEmptyFile(final Path path) throws IOException {
309           assertTrue(path + " should exists", fs.exists(path));
310           assertTrue(path + " should not be empty", fs.getFileStatus(path).getLen() > 0);
311         }
312     });
313   }
314 
315   private Set<String> listFiles(final FileSystem fs, final Path root, final Path dir)
316       throws IOException {
317     Set<String> files = new HashSet<String>();
318     int rootPrefix = root.toString().length();
319     FileStatus[] list = FSUtils.listStatus(fs, dir);
320     if (list != null) {
321       for (FileStatus fstat: list) {
322         LOG.debug(fstat.getPath());
323         if (fstat.isDir()) {
324           files.addAll(listFiles(fs, root, fstat.getPath()));
325         } else {
326           files.add(fstat.getPath().toString().substring(rootPrefix));
327         }
328       }
329     }
330     return files;
331   }
332 }