1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.FSDataOutputStream;
35 import org.apache.hadoop.fs.FileStatus;
36 import org.apache.hadoop.fs.FileSystem;
37 import org.apache.hadoop.fs.FileUtil;
38 import org.apache.hadoop.fs.Path;
39 import org.apache.hadoop.hbase.HBaseTestingUtility;
40 import org.apache.hadoop.hbase.HColumnDescriptor;
41 import org.apache.hadoop.hbase.HConstants;
42 import org.apache.hadoop.hbase.HRegionInfo;
43 import org.apache.hadoop.hbase.HTableDescriptor;
44 import org.apache.hadoop.hbase.testclassification.MediumTests;
45 import org.apache.hadoop.hbase.TableName;
46 import org.apache.hadoop.hbase.client.HBaseAdmin;
47 import org.apache.hadoop.hbase.client.HTable;
48 import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
49 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
50 import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos.SnapshotFileInfo;
51 import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
52 import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
53 import org.apache.hadoop.hbase.util.Bytes;
54 import org.apache.hadoop.hbase.util.FSUtils;
55 import org.apache.hadoop.hbase.util.FSTableDescriptors;
56 import org.apache.hadoop.hbase.util.Pair;
57 import org.junit.After;
58 import org.junit.AfterClass;
59 import org.junit.Before;
60 import org.junit.BeforeClass;
61 import org.junit.Test;
62 import org.junit.experimental.categories.Category;
63
64
65
66
67 @Category(MediumTests.class)
68 public class TestExportSnapshot {
69 private final Log LOG = LogFactory.getLog(getClass());
70
71 protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
72
73 private final static byte[] FAMILY = Bytes.toBytes("cf");
74
75 private byte[] emptySnapshotName;
76 private byte[] snapshotName;
77 private TableName tableName;
78 private HBaseAdmin admin;
79
80 public static void setUpBaseConf(Configuration conf) {
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.max.attempts", 10);
87 conf.setInt("mapred.map.max.attempts", 10);
88 }
89
90 @BeforeClass
91 public static void setUpBeforeClass() throws Exception {
92 setUpBaseConf(TEST_UTIL.getConfiguration());
93 TEST_UTIL.startMiniCluster(3);
94 TEST_UTIL.startMiniMapReduceCluster();
95 }
96
97 @AfterClass
98 public static void tearDownAfterClass() throws Exception {
99 TEST_UTIL.shutdownMiniMapReduceCluster();
100 TEST_UTIL.shutdownMiniCluster();
101 }
102
103
104
105
106 @Before
107 public void setUp() throws Exception {
108 this.admin = TEST_UTIL.getHBaseAdmin();
109
110 long tid = System.currentTimeMillis();
111 tableName = TableName.valueOf("testtb-" + tid);
112 snapshotName = Bytes.toBytes("snaptb0-" + tid);
113 emptySnapshotName = Bytes.toBytes("emptySnaptb0-" + tid);
114
115
116 SnapshotTestingUtils.createTable(TEST_UTIL, tableName, FAMILY);
117
118
119 admin.snapshot(emptySnapshotName, tableName);
120
121
122 HTable table = new HTable(TEST_UTIL.getConfiguration(), tableName);
123 SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 500, FAMILY);
124
125
126 admin.snapshot(snapshotName, tableName);
127 }
128
129 @After
130 public void tearDown() throws Exception {
131 TEST_UTIL.deleteTable(tableName);
132 SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getHBaseAdmin());
133 SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
134 }
135
136
137
138
139
140
141
142
143
144
145
146 @Test
147 public void testBalanceSplit() throws Exception {
148
149 List<Pair<SnapshotFileInfo, Long>> files = new ArrayList<Pair<SnapshotFileInfo, Long>>();
150 for (long i = 0; i <= 20; i++) {
151 SnapshotFileInfo fileInfo = SnapshotFileInfo.newBuilder()
152 .setType(SnapshotFileInfo.Type.HFILE)
153 .setHfile("file-" + i)
154 .build();
155 files.add(new Pair<SnapshotFileInfo, Long>(fileInfo, i));
156 }
157
158
159
160
161
162
163
164 List<List<Pair<SnapshotFileInfo, Long>>> splits = ExportSnapshot.getBalancedSplits(files, 5);
165 assertEquals(5, splits.size());
166
167 String[] split0 = new String[] {"file-20", "file-11", "file-10", "file-1", "file-0"};
168 verifyBalanceSplit(splits.get(0), split0, 42);
169 String[] split1 = new String[] {"file-19", "file-12", "file-9", "file-2"};
170 verifyBalanceSplit(splits.get(1), split1, 42);
171 String[] split2 = new String[] {"file-18", "file-13", "file-8", "file-3"};
172 verifyBalanceSplit(splits.get(2), split2, 42);
173 String[] split3 = new String[] {"file-17", "file-14", "file-7", "file-4"};
174 verifyBalanceSplit(splits.get(3), split3, 42);
175 String[] split4 = new String[] {"file-16", "file-15", "file-6", "file-5"};
176 verifyBalanceSplit(splits.get(4), split4, 42);
177 }
178
179 private void verifyBalanceSplit(final List<Pair<SnapshotFileInfo, Long>> split,
180 final String[] expected, final long expectedSize) {
181 assertEquals(expected.length, split.size());
182 long totalSize = 0;
183 for (int i = 0; i < expected.length; ++i) {
184 Pair<SnapshotFileInfo, Long> fileInfo = split.get(i);
185 assertEquals(expected[i], fileInfo.getFirst().getHfile());
186 totalSize += fileInfo.getSecond();
187 }
188 assertEquals(expectedSize, totalSize);
189 }
190
191
192
193
194 @Test
195 public void testExportFileSystemState() throws Exception {
196 testExportFileSystemState(tableName, snapshotName, snapshotName, 2);
197 }
198
199 @Test
200 public void testExportFileSystemStateWithSkipTmp() throws Exception {
201 TEST_UTIL.getConfiguration().setBoolean(ExportSnapshot.CONF_SKIP_TMP, true);
202 testExportFileSystemState(tableName, snapshotName, snapshotName, 2);
203 }
204
205 @Test
206 public void testEmptyExportFileSystemState() throws Exception {
207 testExportFileSystemState(tableName, emptySnapshotName, emptySnapshotName, 1);
208 }
209
210 @Test
211 public void testConsecutiveExports() throws Exception {
212 Path copyDir = getLocalDestinationDir();
213 testExportFileSystemState(tableName, snapshotName, snapshotName, 2, copyDir, false);
214 testExportFileSystemState(tableName, snapshotName, snapshotName, 2, copyDir, true);
215 removeExportDir(copyDir);
216 }
217
218 @Test
219 public void testExportWithTargetName() throws Exception {
220 final byte[] targetName = Bytes.toBytes("testExportWithTargetName");
221 testExportFileSystemState(tableName, snapshotName, targetName, 2);
222 }
223
224
225
226
227
228 @Test
229 public void testSnapshotWithRefsExportFileSystemState() throws Exception {
230 Configuration conf = TEST_UTIL.getConfiguration();
231
232 final TableName tableWithRefsName =
233 TableName.valueOf("tableWithRefs");
234 final String snapshotName = "tableWithRefs";
235 final String TEST_FAMILY = Bytes.toString(FAMILY);
236 final String TEST_HFILE = "abc";
237
238 final SnapshotDescription sd = SnapshotDescription.newBuilder()
239 .setName(snapshotName)
240 .setTable(tableWithRefsName.getNameAsString()).build();
241
242 FileSystem fs = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
243 Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
244 Path archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
245
246
247 HRegionInfo hri = new HRegionInfo(tableWithRefsName);
248 HRegionFileSystem r0fs = HRegionFileSystem.createRegionOnFileSystem(conf,
249 fs, FSUtils.getTableDir(archiveDir, hri.getTable()), hri);
250 Path storeFile = new Path(rootDir, TEST_HFILE);
251 FSDataOutputStream out = fs.create(storeFile);
252 out.write(Bytes.toBytes("Test Data"));
253 out.close();
254 r0fs.commitStoreFile(TEST_FAMILY, storeFile);
255
256
257
258 hri = new HRegionInfo(tableWithRefsName);
259 HRegionFileSystem r1fs = HRegionFileSystem.createRegionOnFileSystem(conf,
260 fs, new Path(archiveDir, hri.getTable().getNameAsString()), hri);
261 storeFile = new Path(rootDir, TEST_HFILE + '.' + r0fs.getRegionInfo().getEncodedName());
262 out = fs.create(storeFile);
263 out.write(Bytes.toBytes("Test Data"));
264 out.close();
265 r1fs.commitStoreFile(TEST_FAMILY, storeFile);
266
267 Path tableDir = FSUtils.getTableDir(archiveDir, tableWithRefsName);
268 HTableDescriptor htd = new HTableDescriptor(tableWithRefsName);
269 htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
270 new FSTableDescriptors(conf, fs, rootDir)
271 .createTableDescriptorForTableDirectory(tableDir, htd, false);
272
273 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
274 FileUtil.copy(fs, tableDir, fs, snapshotDir, false, conf);
275 SnapshotDescriptionUtils.writeSnapshotInfo(sd, snapshotDir, fs);
276
277 byte[] name = Bytes.toBytes(snapshotName);
278 testExportFileSystemState(tableWithRefsName, name, name, 2);
279 }
280
281 private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName,
282 final byte[] targetName, int filesExpected) throws Exception {
283 Path copyDir = getHdfsDestinationDir();
284 testExportFileSystemState(tableName, snapshotName, targetName, filesExpected, copyDir, false);
285 removeExportDir(copyDir);
286 }
287
288
289
290
291 private void testExportFileSystemState(final TableName tableName, final byte[] snapshotName,
292 final byte[] targetName, int filesExpected, Path copyDir, boolean overwrite)
293 throws Exception {
294 URI hdfsUri = FileSystem.get(TEST_UTIL.getConfiguration()).getUri();
295 FileSystem fs = FileSystem.get(copyDir.toUri(), new Configuration());
296 copyDir = copyDir.makeQualified(fs);
297
298 List<String> opts = new ArrayList<String>();
299 opts.add("-snapshot");
300 opts.add(Bytes.toString(snapshotName));
301 opts.add("-copy-to");
302 opts.add(copyDir.toString());
303 if (targetName != snapshotName) {
304 opts.add("-target");
305 opts.add(Bytes.toString(targetName));
306 }
307 if (overwrite) opts.add("-overwrite");
308
309
310 int res = ExportSnapshot.innerMain(TEST_UTIL.getConfiguration(),
311 opts.toArray(new String[opts.size()]));
312 assertEquals(0, res);
313
314
315 FileStatus[] rootFiles = fs.listStatus(copyDir);
316 assertEquals(filesExpected, rootFiles.length);
317 for (FileStatus fileStatus: rootFiles) {
318 String name = fileStatus.getPath().getName();
319 assertTrue(fileStatus.isDir());
320 assertTrue(name.equals(HConstants.SNAPSHOT_DIR_NAME) ||
321 name.equals(HConstants.HFILE_ARCHIVE_DIRECTORY));
322 }
323
324
325 final FileSystem hdfs = FileSystem.get(hdfsUri, TEST_UTIL.getConfiguration());
326 final Path snapshotDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(snapshotName));
327 final Path targetDir = new Path(HConstants.SNAPSHOT_DIR_NAME, Bytes.toString(targetName));
328 verifySnapshot(hdfs, new Path(TEST_UTIL.getDefaultRootDirPath(), snapshotDir),
329 fs, new Path(copyDir, targetDir));
330 verifyArchive(fs, copyDir, tableName, Bytes.toString(targetName));
331 FSUtils.logFileSystemState(hdfs, snapshotDir, LOG);
332 }
333
334
335
336
337 @Test
338 public void testExportFailure() throws Exception {
339 assertEquals(1, runExportAndInjectFailures(snapshotName, false));
340 }
341
342
343
344
345 @Test
346 public void testExportRetry() throws Exception {
347 assertEquals(0, runExportAndInjectFailures(snapshotName, true));
348 }
349
350
351
352
353 private int runExportAndInjectFailures(final byte[] snapshotName, boolean retry)
354 throws Exception {
355 Path copyDir = getLocalDestinationDir();
356 URI hdfsUri = FileSystem.get(TEST_UTIL.getConfiguration()).getUri();
357 FileSystem fs = FileSystem.get(copyDir.toUri(), new Configuration());
358 copyDir = copyDir.makeQualified(fs);
359
360 Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
361 conf.setBoolean(ExportSnapshot.CONF_TEST_FAILURE, true);
362 conf.setBoolean(ExportSnapshot.CONF_TEST_RETRY, retry);
363
364
365 Path sourceDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
366 int res = ExportSnapshot.innerMain(conf, new String[] {
367 "-snapshot", Bytes.toString(snapshotName),
368 "-copy-from", sourceDir.toString(),
369 "-copy-to", copyDir.toString()
370 });
371 return res;
372 }
373
374
375
376
377 private void verifySnapshot(final FileSystem fs1, final Path root1,
378 final FileSystem fs2, final Path root2) throws IOException {
379 Set<String> s = new HashSet<String>();
380 assertEquals(listFiles(fs1, root1, root1), listFiles(fs2, root2, root2));
381 }
382
383
384
385
386 private void verifyArchive(final FileSystem fs, final Path rootDir,
387 final TableName tableName, final String snapshotName) throws IOException {
388 final Path exportedSnapshot = new Path(rootDir,
389 new Path(HConstants.SNAPSHOT_DIR_NAME, snapshotName));
390 final Path exportedArchive = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
391 LOG.debug(listFiles(fs, exportedArchive, exportedArchive));
392 SnapshotReferenceUtil.visitReferencedFiles(TEST_UTIL.getConfiguration(), fs, exportedSnapshot,
393 new SnapshotReferenceUtil.SnapshotVisitor() {
394 public void storeFile(final HRegionInfo regionInfo, final String family,
395 final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
396 String hfile = storeFile.getName();
397 verifyNonEmptyFile(new Path(exportedArchive,
398 new Path(FSUtils.getTableDir(new Path("./"), tableName),
399 new Path(regionInfo.getEncodedName(), new Path(family, hfile)))));
400 }
401
402 public void recoveredEdits (final String region, final String logfile)
403 throws IOException {
404 verifyNonEmptyFile(new Path(exportedSnapshot,
405 new Path(tableName.getNameAsString(), new Path(region, logfile))));
406 }
407
408 public void logFile (final String server, final String logfile)
409 throws IOException {
410 verifyNonEmptyFile(new Path(exportedSnapshot, new Path(server, logfile)));
411 }
412
413 private void verifyNonEmptyFile(final Path path) throws IOException {
414 assertTrue(path + " should exists", fs.exists(path));
415 assertTrue(path + " should not be empty", fs.getFileStatus(path).getLen() > 0);
416 }
417 });
418
419
420 SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, exportedSnapshot);
421 assertTrue(desc.getName().equals(snapshotName));
422 assertTrue(desc.getTable().equals(tableName.getNameAsString()));
423 }
424
425 private Set<String> listFiles(final FileSystem fs, final Path root, final Path dir)
426 throws IOException {
427 Set<String> files = new HashSet<String>();
428 int rootPrefix = root.toString().length();
429 FileStatus[] list = FSUtils.listStatus(fs, dir);
430 if (list != null) {
431 for (FileStatus fstat: list) {
432 LOG.debug(fstat.getPath());
433 if (fstat.isDir()) {
434 files.addAll(listFiles(fs, root, fstat.getPath()));
435 } else {
436 files.add(fstat.getPath().toString().substring(rootPrefix));
437 }
438 }
439 }
440 return files;
441 }
442
443 private Path getHdfsDestinationDir() {
444 Path rootDir = TEST_UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
445 Path path = new Path(new Path(rootDir, "export-test"), "export-" + System.currentTimeMillis());
446 LOG.info("HDFS export destination path: " + path);
447 return path;
448 }
449
450 private Path getLocalDestinationDir() {
451 Path path = TEST_UTIL.getDataTestDir("local-export-" + System.currentTimeMillis());
452 LOG.info("Local export destination path: " + path);
453 return path;
454 }
455
456 private void removeExportDir(final Path path) throws IOException {
457 FileSystem fs = FileSystem.get(path.toUri(), new Configuration());
458 FSUtils.logFileSystemState(fs, path, LOG);
459 fs.delete(path, true);
460 }
461 }