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.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
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
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
117 SnapshotTestingUtils.createTable(TEST_UTIL, tableName, FAMILY);
118
119
120 admin.snapshot(emptySnapshotName, tableName);
121
122
123 SnapshotTestingUtils.loadData(TEST_UTIL, tableName, 50, FAMILY);
124 tableNumFiles = admin.getTableRegions(tableName).size();
125
126
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
140
141
142
143
144
145
146
147 @Test
148 public void testBalanceSplit() throws Exception {
149
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
160
161
162
163
164
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
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
227
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
248
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
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
291 int res = ExportSnapshot.innerMain(TEST_UTIL.getConfiguration(),
292 opts.toArray(new String[opts.size()]));
293 assertEquals(0, res);
294
295
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
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
317
318 @Test
319 public void testExportFailure() throws Exception {
320 assertEquals(1, runExportAndInjectFailures(snapshotName, false));
321 }
322
323
324
325
326 @Test
327 public void testExportRetry() throws Exception {
328 assertEquals(0, runExportAndInjectFailures(snapshotName, true));
329 }
330
331
332
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
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
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
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
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
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 }