1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.backup.example;
19
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.when;
25
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.concurrent.CountDownLatch;
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.ChoreService;
38 import org.apache.hadoop.hbase.HBaseTestingUtility;
39 import org.apache.hadoop.hbase.HColumnDescriptor;
40 import org.apache.hadoop.hbase.HConstants;
41 import org.apache.hadoop.hbase.master.cleaner.DirScanPool;
42 import org.apache.hadoop.hbase.testclassification.MediumTests;
43 import org.apache.hadoop.hbase.Stoppable;
44 import org.apache.hadoop.hbase.client.ClusterConnection;
45 import org.apache.hadoop.hbase.client.ConnectionFactory;
46 import org.apache.hadoop.hbase.client.Put;
47 import org.apache.hadoop.hbase.master.cleaner.BaseHFileCleanerDelegate;
48 import org.apache.hadoop.hbase.master.cleaner.HFileCleaner;
49 import org.apache.hadoop.hbase.regionserver.CompactedHFilesDischarger;
50 import org.apache.hadoop.hbase.regionserver.HRegion;
51 import org.apache.hadoop.hbase.regionserver.Region;
52 import org.apache.hadoop.hbase.regionserver.RegionServerServices;
53 import org.apache.hadoop.hbase.regionserver.Store;
54 import org.apache.hadoop.hbase.util.Bytes;
55 import org.apache.hadoop.hbase.util.FSUtils;
56 import org.apache.hadoop.hbase.util.HFileArchiveUtil;
57 import org.apache.hadoop.hbase.util.StoppableImplementation;
58 import org.apache.hadoop.hbase.zookeeper.ZKUtil;
59 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
60 import org.apache.zookeeper.KeeperException;
61 import org.junit.After;
62 import org.junit.AfterClass;
63 import org.junit.BeforeClass;
64 import org.junit.Test;
65 import org.junit.experimental.categories.Category;
66 import org.mockito.Mockito;
67 import org.mockito.invocation.InvocationOnMock;
68 import org.mockito.stubbing.Answer;
69
70
71
72
73
74 @Category(MediumTests.class)
75 public class TestZooKeeperTableArchiveClient {
76
77 private static final Log LOG = LogFactory.getLog(TestZooKeeperTableArchiveClient.class);
78 private static final HBaseTestingUtility UTIL = HBaseTestingUtility.createLocalHTU();
79 private static final String STRING_TABLE_NAME = "test";
80 private static final byte[] TEST_FAM = Bytes.toBytes("fam");
81 private static final byte[] TABLE_NAME = Bytes.toBytes(STRING_TABLE_NAME);
82 private static ZKTableArchiveClient archivingClient;
83 private final List<Path> toCleanup = new ArrayList<Path>();
84 private static ClusterConnection CONNECTION;
85 private static RegionServerServices rss;
86 private static DirScanPool POOL;
87
88
89
90
91 @BeforeClass
92 public static void setupCluster() throws Exception {
93 setupConf(UTIL.getConfiguration());
94 UTIL.startMiniZKCluster();
95 CONNECTION = (ClusterConnection)ConnectionFactory.createConnection(UTIL.getConfiguration());
96 archivingClient = new ZKTableArchiveClient(UTIL.getConfiguration(), CONNECTION);
97
98 ZooKeeperWatcher watcher = UTIL.getZooKeeperWatcher();
99 String archivingZNode = ZKTableArchiveClient.getArchiveZNode(UTIL.getConfiguration(), watcher);
100 ZKUtil.createWithParents(watcher, archivingZNode);
101 rss = mock(RegionServerServices.class);
102 POOL= new DirScanPool(UTIL.getConfiguration());
103 }
104
105 private static void setupConf(Configuration conf) {
106
107 conf.setInt("hbase.hstore.compaction.min", 3);
108 }
109
110 @After
111 public void tearDown() throws Exception {
112 try {
113 FileSystem fs = UTIL.getTestFileSystem();
114
115 for (Path file : toCleanup) {
116
117 FSUtils.delete(fs, file, true);
118 }
119 } catch (IOException e) {
120 LOG.warn("Failure to delete archive directory", e);
121 } finally {
122 toCleanup.clear();
123 }
124
125 archivingClient.disableHFileBackup();
126 }
127
128 @AfterClass
129 public static void cleanupTest() throws Exception {
130 CONNECTION.close();
131 UTIL.shutdownMiniZKCluster();
132 POOL.shutdownNow();
133 }
134
135
136
137
138 @Test (timeout=300000)
139 public void testArchivingEnableDisable() throws Exception {
140
141 LOG.debug("----Starting archiving");
142 archivingClient.enableHFileBackupAsync(TABLE_NAME);
143 assertTrue("Archving didn't get turned on", archivingClient
144 .getArchivingEnabled(TABLE_NAME));
145
146
147 archivingClient.disableHFileBackup();
148 assertFalse("Archving didn't get turned off.", archivingClient.getArchivingEnabled(TABLE_NAME));
149
150
151 archivingClient.enableHFileBackupAsync(TABLE_NAME);
152 assertTrue("Archving didn't get turned on", archivingClient
153 .getArchivingEnabled(TABLE_NAME));
154
155
156 archivingClient.disableHFileBackup(TABLE_NAME);
157 assertFalse("Archving didn't get turned off for " + STRING_TABLE_NAME,
158 archivingClient.getArchivingEnabled(TABLE_NAME));
159 }
160
161 @Test (timeout=300000)
162 public void testArchivingOnSingleTable() throws Exception {
163 createArchiveDirectory();
164 FileSystem fs = UTIL.getTestFileSystem();
165 Path archiveDir = getArchiveDir();
166 Path tableDir = getTableDir(STRING_TABLE_NAME);
167 toCleanup.add(archiveDir);
168 toCleanup.add(tableDir);
169
170 Configuration conf = UTIL.getConfiguration();
171
172 Stoppable stop = new StoppableImplementation();
173 HFileCleaner cleaner = setupAndCreateCleaner(conf, fs, archiveDir, stop);
174 List<BaseHFileCleanerDelegate> cleaners = turnOnArchiving(STRING_TABLE_NAME, cleaner);
175 final LongTermArchivingHFileCleaner delegate = (LongTermArchivingHFileCleaner) cleaners.get(0);
176
177
178 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAM);
179 HRegion region = UTIL.createTestRegion(STRING_TABLE_NAME, hcd);
180 List<Region> regions = new ArrayList<Region>();
181 regions.add(region);
182 when(rss.getOnlineRegions()).thenReturn(regions);
183 final CompactedHFilesDischarger compactionCleaner =
184 new CompactedHFilesDischarger(100, stop, rss, false);
185 loadFlushAndCompact(region, TEST_FAM);
186 compactionCleaner.chore();
187
188 List<Path> files = getAllFiles(fs, archiveDir);
189 if (files == null) {
190 FSUtils.logFileSystemState(fs, UTIL.getDataTestDir(), LOG);
191 throw new RuntimeException("Didn't archive any files!");
192 }
193 CountDownLatch finished = setupCleanerWatching(delegate, cleaners, files.size());
194
195 runCleaner(cleaner, finished, stop);
196
197
198 List<Path> archivedFiles = getAllFiles(fs, archiveDir);
199 assertEquals("Archived files changed after running archive cleaner.", files, archivedFiles);
200
201
202 assertTrue(fs.exists(HFileArchiveUtil.getArchivePath(UTIL.getConfiguration())));
203 }
204
205
206
207
208
209 @Test (timeout=300000)
210 public void testMultipleTables() throws Exception {
211 createArchiveDirectory();
212 String otherTable = "otherTable";
213
214 FileSystem fs = UTIL.getTestFileSystem();
215 Path archiveDir = getArchiveDir();
216 Path tableDir = getTableDir(STRING_TABLE_NAME);
217 Path otherTableDir = getTableDir(otherTable);
218
219
220 toCleanup.add(archiveDir);
221 toCleanup.add(tableDir);
222 toCleanup.add(otherTableDir);
223 Configuration conf = UTIL.getConfiguration();
224
225 Stoppable stop = new StoppableImplementation();
226 final ChoreService choreService = new ChoreService("TEST_SERVER_NAME");
227 HFileCleaner cleaner = setupAndCreateCleaner(conf, fs, archiveDir, stop);
228 List<BaseHFileCleanerDelegate> cleaners = turnOnArchiving(STRING_TABLE_NAME, cleaner);
229 final LongTermArchivingHFileCleaner delegate = (LongTermArchivingHFileCleaner) cleaners.get(0);
230
231 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAM);
232 HRegion region = UTIL.createTestRegion(STRING_TABLE_NAME, hcd);
233 List<Region> regions = new ArrayList<Region>();
234 regions.add(region);
235 when(rss.getOnlineRegions()).thenReturn(regions);
236 final CompactedHFilesDischarger compactionCleaner =
237 new CompactedHFilesDischarger(100, stop, rss, false);
238 loadFlushAndCompact(region, TEST_FAM);
239 compactionCleaner.chore();
240
241 hcd = new HColumnDescriptor(TEST_FAM);
242 HRegion otherRegion = UTIL.createTestRegion(otherTable, hcd);
243 regions = new ArrayList<Region>();
244 regions.add(otherRegion);
245 when(rss.getOnlineRegions()).thenReturn(regions);
246 final CompactedHFilesDischarger compactionCleaner1 = new CompactedHFilesDischarger(100, stop,
247 rss, false);
248 loadFlushAndCompact(otherRegion, TEST_FAM);
249 compactionCleaner1.chore();
250
251
252 List<Path> files = getAllFiles(fs, archiveDir);
253 if (files == null) {
254 FSUtils.logFileSystemState(fs, archiveDir, LOG);
255 throw new RuntimeException("Didn't load archive any files!");
256 }
257
258
259 int initialCountForPrimary = 0;
260 int initialCountForOtherTable = 0;
261 for (Path file : files) {
262 String tableName = file.getParent().getParent().getParent().getName();
263
264 if (tableName.equals(otherTable)) initialCountForOtherTable++;
265 else if (tableName.equals(STRING_TABLE_NAME)) initialCountForPrimary++;
266 }
267
268 assertTrue("Didn't archive files for:" + STRING_TABLE_NAME, initialCountForPrimary > 0);
269 assertTrue("Didn't archive files for:" + otherTable, initialCountForOtherTable > 0);
270
271
272
273 CountDownLatch finished = setupCleanerWatching(delegate, cleaners, files.size() + 3);
274
275 choreService.scheduleChore(cleaner);
276
277 finished.await();
278
279 stop.stop("");
280
281
282 List<Path> archivedFiles = getAllFiles(fs, archiveDir);
283 int archivedForPrimary = 0;
284 for(Path file: archivedFiles) {
285 String tableName = file.getParent().getParent().getParent().getName();
286
287 assertFalse("Have a file from the non-archived table: " + file, tableName.equals(otherTable));
288 if (tableName.equals(STRING_TABLE_NAME)) archivedForPrimary++;
289 }
290
291 assertEquals("Not all archived files for the primary table were retained.", initialCountForPrimary,
292 archivedForPrimary);
293
294
295 assertTrue("Archive directory was deleted via archiver", fs.exists(archiveDir));
296 }
297
298
299 private void createArchiveDirectory() throws IOException {
300
301 FileSystem fs = UTIL.getTestFileSystem();
302 Path archiveDir = getArchiveDir();
303 fs.mkdirs(archiveDir);
304 }
305
306 private Path getArchiveDir() throws IOException {
307 return new Path(UTIL.getDataTestDir(), HConstants.HFILE_ARCHIVE_DIRECTORY);
308 }
309
310 private Path getTableDir(String tableName) throws IOException {
311 Path testDataDir = UTIL.getDataTestDir();
312 FSUtils.setRootDir(UTIL.getConfiguration(), testDataDir);
313 return new Path(testDataDir, tableName);
314 }
315
316 private HFileCleaner setupAndCreateCleaner(Configuration conf, FileSystem fs, Path archiveDir,
317 Stoppable stop) {
318 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS,
319 LongTermArchivingHFileCleaner.class.getCanonicalName());
320 return new HFileCleaner(1000, stop, conf, fs, archiveDir, POOL);
321 }
322
323
324
325
326
327
328
329
330
331 private List<BaseHFileCleanerDelegate> turnOnArchiving(String tableName, HFileCleaner cleaner)
332 throws IOException, KeeperException {
333
334 LOG.debug("----Starting archiving for table:" + tableName);
335 archivingClient.enableHFileBackupAsync(Bytes.toBytes(tableName));
336 assertTrue("Archving didn't get turned on", archivingClient.getArchivingEnabled(tableName));
337
338
339 List<BaseHFileCleanerDelegate> cleaners = cleaner.getDelegatesForTesting();
340 LongTermArchivingHFileCleaner delegate = (LongTermArchivingHFileCleaner) cleaners.get(0);
341 while (!delegate.archiveTracker.keepHFiles(STRING_TABLE_NAME)) {
342
343 }
344 return cleaners;
345 }
346
347
348
349
350
351
352
353 private CountDownLatch setupCleanerWatching(LongTermArchivingHFileCleaner cleaner,
354 List<BaseHFileCleanerDelegate> cleaners, final int expected) {
355
356 BaseHFileCleanerDelegate delegateSpy = Mockito.spy(cleaner);
357 final int[] counter = new int[] { 0 };
358 final CountDownLatch finished = new CountDownLatch(1);
359 Mockito.doAnswer(new Answer<Iterable<FileStatus>>() {
360
361 @Override
362 public Iterable<FileStatus> answer(InvocationOnMock invocation) throws Throwable {
363 counter[0]++;
364 LOG.debug(counter[0] + "/ " + expected + ") Wrapping call to getDeletableFiles for files: "
365 + invocation.getArguments()[0]);
366
367 @SuppressWarnings("unchecked")
368 Iterable<FileStatus> ret = (Iterable<FileStatus>) invocation.callRealMethod();
369 if (counter[0] >= expected) finished.countDown();
370 return ret;
371 }
372 }).when(delegateSpy).getDeletableFiles(Mockito.anyListOf(FileStatus.class));
373 cleaners.set(0, delegateSpy);
374
375 return finished;
376 }
377
378
379
380
381
382
383 private List<Path> getAllFiles(FileSystem fs, Path dir) throws IOException {
384 FileStatus[] files = FSUtils.listStatus(fs, dir, null);
385 if (files == null) {
386 LOG.warn("No files under:" + dir);
387 return null;
388 }
389
390 List<Path> allFiles = new ArrayList<Path>();
391 for (FileStatus file : files) {
392 if (file.isDirectory()) {
393 List<Path> subFiles = getAllFiles(fs, file.getPath());
394 if (subFiles != null) allFiles.addAll(subFiles);
395 continue;
396 }
397 allFiles.add(file.getPath());
398 }
399 return allFiles;
400 }
401
402 private void loadFlushAndCompact(Region region, byte[] family) throws IOException {
403
404 createHFileInRegion(region, family);
405 createHFileInRegion(region, family);
406
407 Store s = region.getStore(family);
408 int count = s.getStorefilesCount();
409 assertTrue("Don't have the expected store files, wanted >= 2 store files, but was:" + count,
410 count >= 2);
411
412
413 LOG.debug("Compacting stores");
414 region.compact(true);
415 }
416
417
418
419
420
421
422
423 private void createHFileInRegion(Region region, byte[] columnFamily) throws IOException {
424
425 Put p = new Put(Bytes.toBytes("row"));
426 p.add(columnFamily, Bytes.toBytes("Qual"), Bytes.toBytes("v1"));
427 region.put(p);
428
429 region.flush(true);
430 }
431
432
433
434
435 private void runCleaner(HFileCleaner cleaner, CountDownLatch finished, Stoppable stop)
436 throws InterruptedException {
437 final ChoreService choreService = new ChoreService("CLEANER_SERVER_NAME");
438
439 choreService.scheduleChore(cleaner);
440
441 finished.await();
442
443 stop.stop("");
444 }
445 }