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