1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.master.cleaner;
19
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23
24 import java.io.IOException;
25 import java.util.Random;
26 import java.util.concurrent.atomic.AtomicBoolean;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.hadoop.conf.Configuration;
31 import org.apache.hadoop.fs.FSDataOutputStream;
32 import org.apache.hadoop.fs.FileStatus;
33 import org.apache.hadoop.fs.FileSystem;
34 import org.apache.hadoop.fs.FilterFileSystem;
35 import org.apache.hadoop.fs.Path;
36 import org.apache.hadoop.hbase.HBaseTestingUtility;
37 import org.apache.hadoop.hbase.testclassification.SmallTests;
38 import org.apache.hadoop.hbase.Stoppable;
39 import org.apache.hadoop.hbase.util.FSUtils;
40 import org.apache.hadoop.hbase.util.StoppableImplementation;
41 import org.junit.AfterClass;
42 import org.junit.BeforeClass;
43 import org.junit.Test;
44 import org.junit.experimental.categories.Category;
45 import org.mockito.Mockito;
46 import org.mockito.invocation.InvocationOnMock;
47 import org.mockito.stubbing.Answer;
48
49 @Category(SmallTests.class)
50 public class TestCleanerChore {
51
52 private static final Log LOG = LogFactory.getLog(TestCleanerChore.class);
53 private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
54 private static DirScanPool POOL;
55
56 @BeforeClass
57 public static void setup() {
58 POOL = new DirScanPool(UTIL.getConfiguration());
59 }
60
61 @AfterClass
62 public static void cleanup() throws Exception {
63
64 UTIL.cleanupTestDir();
65 POOL.shutdownNow();
66 }
67
68
69 @Test
70 public void testSavesFilesOnRequest() throws Exception {
71 Stoppable stop = new StoppableImplementation();
72 Configuration conf = UTIL.getConfiguration();
73 Path testDir = UTIL.getDataTestDir();
74 FileSystem fs = UTIL.getTestFileSystem();
75 String confKey = "hbase.test.cleaner.delegates";
76 conf.set(confKey, NeverDelete.class.getName());
77
78 AllValidPaths chore =
79 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
80
81
82 Path parent = new Path(testDir, "parent");
83 Path file = new Path(parent, "someFile");
84 fs.mkdirs(parent);
85
86 fs.create(file).close();
87 assertTrue("Test file didn't get created.", fs.exists(file));
88
89
90 chore.chore();
91
92
93 assertTrue("File shouldn't have been deleted", fs.exists(file));
94 assertTrue("directory shouldn't have been deleted", fs.exists(parent));
95 }
96
97 @Test
98 public void retriesIOExceptionInStatus() throws Exception {
99 Stoppable stop = new StoppableImplementation();
100 Configuration conf = UTIL.getConfiguration();
101 Path testDir = UTIL.getDataTestDir();
102 FileSystem fs = UTIL.getTestFileSystem();
103 String confKey = "hbase.test.cleaner.delegates";
104
105 Path child = new Path(testDir, "child");
106 Path file = new Path(child, "file");
107 fs.mkdirs(child);
108 fs.create(file).close();
109 assertTrue("test file didn't get created.", fs.exists(file));
110 final AtomicBoolean fails = new AtomicBoolean(true);
111
112 FilterFileSystem filtered = new FilterFileSystem(fs) {
113 public FileStatus[] listStatus(Path f) throws IOException {
114 if (fails.get()) {
115 throw new IOException("whomp whomp.");
116 }
117 return fs.listStatus(f);
118 }
119 };
120
121 AllValidPaths chore =
122 new AllValidPaths("test-retry-ioe", stop, conf, filtered, testDir, confKey, POOL);
123
124
125 Boolean result = chore.runCleaner();
126
127
128 assertTrue("test rig failed to inject failure.", fs.exists(file));
129 assertTrue("test rig failed to inject failure.", fs.exists(child));
130
131 assertFalse("chore should report that it failed.", result);
132
133
134 fails.set(false);
135 result = chore.runCleaner();
136
137
138 assertFalse("file should have been destroyed.", fs.exists(file));
139 assertFalse("directory should have been destroyed.", fs.exists(child));
140
141 assertTrue("chore should claim it succeeded.", result);
142 }
143
144 @Test
145 public void testDeletesEmptyDirectories() throws Exception {
146 Stoppable stop = new StoppableImplementation();
147 Configuration conf = UTIL.getConfiguration();
148 Path testDir = UTIL.getDataTestDir();
149 FileSystem fs = UTIL.getTestFileSystem();
150 String confKey = "hbase.test.cleaner.delegates";
151 conf.set(confKey, AlwaysDelete.class.getName());
152
153 AllValidPaths chore =
154 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
155
156
157 Path parent = new Path(testDir, "parent");
158 Path child = new Path(parent, "child");
159 Path emptyChild = new Path(parent, "emptyChild");
160 Path file = new Path(child, "someFile");
161 fs.mkdirs(child);
162 fs.mkdirs(emptyChild);
163
164 fs.create(file).close();
165
166 Path topFile = new Path(testDir, "topFile");
167 fs.create(topFile).close();
168 assertTrue("Test file didn't get created.", fs.exists(file));
169 assertTrue("Test file didn't get created.", fs.exists(topFile));
170
171
172 chore.chore();
173
174
175 assertFalse("File didn't get deleted", fs.exists(topFile));
176 assertFalse("File didn't get deleted", fs.exists(file));
177 assertFalse("Empty directory didn't get deleted", fs.exists(child));
178 assertFalse("Empty directory didn't get deleted", fs.exists(parent));
179 }
180
181
182
183
184
185
186 @Test
187 public void testDoesNotCheckDirectories() throws Exception {
188 Stoppable stop = new StoppableImplementation();
189 Configuration conf = UTIL.getConfiguration();
190 Path testDir = UTIL.getDataTestDir();
191 FileSystem fs = UTIL.getTestFileSystem();
192 String confKey = "hbase.test.cleaner.delegates";
193 conf.set(confKey, AlwaysDelete.class.getName());
194
195 AllValidPaths chore =
196 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
197
198 AlwaysDelete delegate = (AlwaysDelete) chore.cleanersChain.get(0);
199 AlwaysDelete spy = Mockito.spy(delegate);
200 chore.cleanersChain.set(0, spy);
201
202
203 Path parent = new Path(testDir, "parent");
204 Path file = new Path(parent, "someFile");
205 fs.mkdirs(parent);
206 assertTrue("Test parent didn't get created.", fs.exists(parent));
207
208 fs.create(file).close();
209 assertTrue("Test file didn't get created.", fs.exists(file));
210
211 FileStatus fStat = fs.getFileStatus(parent);
212 chore.chore();
213
214 Mockito.verify(spy, Mockito.never()).isFileDeletable(fStat);
215 Mockito.reset(spy);
216 }
217
218 @Test
219 public void testStoppedCleanerDoesNotDeleteFiles() throws Exception {
220 Stoppable stop = new StoppableImplementation();
221 Configuration conf = UTIL.getConfiguration();
222 Path testDir = UTIL.getDataTestDir();
223 FileSystem fs = UTIL.getTestFileSystem();
224 String confKey = "hbase.test.cleaner.delegates";
225 conf.set(confKey, AlwaysDelete.class.getName());
226
227 AllValidPaths chore =
228 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
229
230
231 Path topFile = new Path(testDir, "topFile");
232 fs.create(topFile).close();
233 assertTrue("Test file didn't get created.", fs.exists(topFile));
234
235
236 stop.stop("testing stop");
237
238
239 chore.chore();
240
241
242 assertTrue("File got deleted while chore was stopped", fs.exists(topFile));
243 }
244
245
246
247
248
249
250 @Test
251 public void testCleanerDoesNotDeleteDirectoryWithLateAddedFiles() throws IOException {
252 Stoppable stop = new StoppableImplementation();
253 Configuration conf = UTIL.getConfiguration();
254 final Path testDir = UTIL.getDataTestDir();
255 final FileSystem fs = UTIL.getTestFileSystem();
256 String confKey = "hbase.test.cleaner.delegates";
257 conf.set(confKey, AlwaysDelete.class.getName());
258
259 AllValidPaths chore =
260 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
261
262 AlwaysDelete delegate = (AlwaysDelete) chore.cleanersChain.get(0);
263 AlwaysDelete spy = Mockito.spy(delegate);
264 chore.cleanersChain.set(0, spy);
265
266
267 final Path parent = new Path(testDir, "parent");
268 Path file = new Path(parent, "someFile");
269 fs.mkdirs(parent);
270
271 fs.create(file).close();
272 assertTrue("Test file didn't get created.", fs.exists(file));
273 final Path addedFile = new Path(parent, "addedFile");
274
275
276 Mockito.doAnswer(new Answer<Boolean>() {
277 @Override
278 public Boolean answer(InvocationOnMock invocation) throws Throwable {
279 fs.create(addedFile).close();
280 FSUtils.logFileSystemState(fs, testDir, LOG);
281 return (Boolean) invocation.callRealMethod();
282 }
283 }).when(spy).isFileDeletable(Mockito.any(FileStatus.class));
284
285
286 chore.chore();
287
288
289 assertTrue("Added file unexpectedly deleted", fs.exists(addedFile));
290 assertTrue("Parent directory deleted unexpectedly", fs.exists(parent));
291 assertFalse("Original file unexpectedly retained", fs.exists(file));
292 Mockito.verify(spy, Mockito.times(1)).isFileDeletable(Mockito.any(FileStatus.class));
293 Mockito.reset(spy);
294 }
295
296
297
298
299
300
301
302
303
304
305 @Test
306 public void testNoExceptionFromDirectoryWithRacyChildren() throws Exception {
307 UTIL.cleanupTestDir();
308 Stoppable stop = new StoppableImplementation();
309
310
311 HBaseTestingUtility localUtil = new HBaseTestingUtility();
312 Configuration conf = localUtil.getConfiguration();
313 final Path testDir = UTIL.getDataTestDir();
314 final FileSystem fs = UTIL.getTestFileSystem();
315 LOG.debug("Writing test data to: " + testDir);
316 String confKey = "hbase.test.cleaner.delegates";
317 conf.set(confKey, AlwaysDelete.class.getName());
318
319 AllValidPaths chore =
320 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
321
322 AlwaysDelete delegate = (AlwaysDelete) chore.cleanersChain.get(0);
323 AlwaysDelete spy = Mockito.spy(delegate);
324 chore.cleanersChain.set(0, spy);
325
326
327 final Path parent = new Path(testDir, "parent");
328 Path file = new Path(parent, "someFile");
329 fs.mkdirs(parent);
330
331 fs.create(file).close();
332 assertTrue("Test file didn't get created.", fs.exists(file));
333 final Path racyFile = new Path(parent, "addedFile");
334
335
336 Mockito.doAnswer(new Answer<Boolean>() {
337 @Override
338 public Boolean answer(InvocationOnMock invocation) throws Throwable {
339 fs.create(racyFile).close();
340 FSUtils.logFileSystemState(fs, testDir, LOG);
341 return (Boolean) invocation.callRealMethod();
342 }
343 }).when(spy).isFileDeletable(Mockito.any(FileStatus.class));
344
345
346 chore.chore();
347
348
349 assertTrue("Added file unexpectedly deleted", fs.exists(racyFile));
350 assertTrue("Parent directory deleted unexpectedly", fs.exists(parent));
351 assertFalse("Original file unexpectedly retained", fs.exists(file));
352 Mockito.verify(spy, Mockito.times(1)).isFileDeletable(Mockito.any(FileStatus.class));
353 }
354
355 @Test
356 public void testDeleteFileWithCleanerEnabled() throws Exception {
357 Stoppable stop = new StoppableImplementation();
358 Configuration conf = UTIL.getConfiguration();
359 Path testDir = UTIL.getDataTestDir();
360 FileSystem fs = UTIL.getTestFileSystem();
361 String confKey = "hbase.test.cleaner.delegates";
362 conf.set(confKey, AlwaysDelete.class.getName());
363
364 AllValidPaths chore =
365 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
366
367
368 chore.setEnabled(true);
369
370
371 Path parent = new Path(testDir, "parent");
372 Path child = new Path(parent, "child");
373 Path file = new Path(child, "someFile");
374 fs.mkdirs(child);
375
376
377 fs.create(file).close();
378 assertTrue("Test file didn't get created.", fs.exists(file));
379
380
381 chore.chore();
382
383
384 assertFalse("File didn't get deleted", fs.exists(file));
385 assertFalse("Empty directory didn't get deleted", fs.exists(child));
386 assertFalse("Empty directory didn't get deleted", fs.exists(parent));
387 }
388
389 @Test
390 public void testDeleteFileWithCleanerDisabled() throws Exception {
391 Stoppable stop = new StoppableImplementation();
392 Configuration conf = UTIL.getConfiguration();
393 Path testDir = UTIL.getDataTestDir();
394 FileSystem fs = UTIL.getTestFileSystem();
395 String confKey = "hbase.test.cleaner.delegates";
396 conf.set(confKey, AlwaysDelete.class.getName());
397
398 AllValidPaths chore =
399 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
400
401
402 chore.setEnabled(false);
403
404
405 Path parent = new Path(testDir, "parent");
406 Path child = new Path(parent, "child");
407 Path file = new Path(child, "someFile");
408 fs.mkdirs(child);
409
410
411 fs.create(file).close();
412 assertTrue("Test file didn't get created.", fs.exists(file));
413
414
415 chore.chore();
416
417
418 assertTrue("File got deleted with cleaner disabled", fs.exists(file));
419 assertTrue("Directory got deleted", fs.exists(child));
420 assertTrue("Directory got deleted", fs.exists(parent));
421 }
422
423
424 @Test
425 public void testOnConfigurationChange() throws Exception {
426 int availableProcessorNum = Runtime.getRuntime().availableProcessors();
427 if (availableProcessorNum == 1) {
428 return;
429 }
430
431
432 int initPoolSize = availableProcessorNum / 2;
433 int changedPoolSize = availableProcessorNum;
434
435 Stoppable stop = new StoppableImplementation();
436 Configuration conf = UTIL.getConfiguration();
437 Path testDir = UTIL.getDataTestDir();
438 FileSystem fs = UTIL.getTestFileSystem();
439 String confKey = "hbase.test.cleaner.delegates";
440 conf.set(confKey, AlwaysDelete.class.getName());
441 conf.set(CleanerChore.CHORE_POOL_SIZE, String.valueOf(initPoolSize));
442 final AllValidPaths chore =
443 new AllValidPaths("test-file-cleaner", stop, conf, fs, testDir, confKey, POOL);
444 chore.setEnabled(true);
445
446 int dirNums = 6;
447 Path[] subdirs = new Path[dirNums];
448 for (int i = 0; i < dirNums; i++) {
449 subdirs[i] = new Path(testDir, "subdir-" + i);
450 fs.mkdirs(subdirs[i]);
451 }
452
453 for (Path subdir : subdirs) {
454 createFiles(fs, subdir, 6);
455 }
456
457 Thread t = new Thread(new Runnable() {
458 @Override
459 public void run() {
460 chore.chore();
461 }
462 });
463 t.setDaemon(true);
464 t.start();
465
466 conf.set(CleanerChore.CHORE_POOL_SIZE, String.valueOf(changedPoolSize));
467 POOL.onConfigurationChange(conf);
468 assertEquals(changedPoolSize, chore.getChorePoolSize());
469
470 t.join();
471 }
472
473 @Test
474 public void testMinimumNumberOfThreads() throws Exception {
475 Stoppable stop = new StoppableImplementation();
476 Configuration conf = UTIL.getConfiguration();
477 Path testDir = UTIL.getDataTestDir();
478 FileSystem fs = UTIL.getTestFileSystem();
479 String confKey = "hbase.test.cleaner.delegates";
480 conf.set(confKey, AlwaysDelete.class.getName());
481 conf.set(CleanerChore.CHORE_POOL_SIZE, "2");
482 int numProcs = Runtime.getRuntime().availableProcessors();
483
484 assertEquals(numProcs, CleanerChore.calculatePoolSize(Integer.toString(numProcs)));
485
486 assertEquals(numProcs, CleanerChore.calculatePoolSize(Integer.toString(numProcs + 2)));
487
488 assertEquals(1, CleanerChore.calculatePoolSize("0.0"));
489 }
490
491 private void createFiles(FileSystem fs, Path parentDir, int numOfFiles) throws IOException {
492 Random random = new Random();
493 for (int i = 0; i < numOfFiles; i++) {
494 int xMega = 1 + random.nextInt(3);
495 try (FSDataOutputStream fsdos = fs.create(new Path(parentDir, "file-" + i))) {
496 for (int m = 0; m < xMega; m++) {
497 byte[] M = new byte[1024 * 1024];
498 random.nextBytes(M);
499 fsdos.write(M);
500 }
501 }
502 }
503 }
504
505 private static class AllValidPaths extends CleanerChore<BaseHFileCleanerDelegate> {
506
507 public AllValidPaths(String name, Stoppable s, Configuration conf, FileSystem fs,
508 Path oldFileDir, String confkey, DirScanPool pool) {
509 super(name, Integer.MAX_VALUE, s, conf, fs, oldFileDir, confkey, pool);
510 }
511
512
513 @Override
514 protected boolean validate(Path file) {
515 return true;
516 }
517 };
518
519 public static class AlwaysDelete extends BaseHFileCleanerDelegate {
520 @Override
521 public boolean isFileDeletable(FileStatus fStat) {
522 return true;
523 }
524 }
525
526 public static class NeverDelete extends BaseHFileCleanerDelegate {
527 @Override
528 public boolean isFileDeletable(FileStatus fStat) {
529 return false;
530 }
531 }
532 }