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