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.List;
26 import java.util.Random;
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.Path;
35 import org.apache.hadoop.hbase.ChoreService;
36 import org.apache.hadoop.hbase.CoordinatedStateManager;
37 import org.apache.hadoop.hbase.HBaseTestingUtility;
38 import org.apache.hadoop.hbase.HConstants;
39 import org.apache.hadoop.hbase.Server;
40 import org.apache.hadoop.hbase.ServerName;
41 import org.apache.hadoop.hbase.client.ClusterConnection;
42 import org.apache.hadoop.hbase.testclassification.MediumTests;
43 import org.apache.hadoop.hbase.util.EnvironmentEdge;
44 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
45 import org.apache.hadoop.hbase.zookeeper.MetaTableLocator;
46 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
47 import org.junit.AfterClass;
48 import org.junit.Assert;
49 import org.junit.BeforeClass;
50 import org.junit.Test;
51 import org.junit.experimental.categories.Category;
52
53 @Category(MediumTests.class)
54 public class TestHFileCleaner {
55 private static final Log LOG = LogFactory.getLog(TestHFileCleaner.class);
56
57 private final static HBaseTestingUtility UTIL = new HBaseTestingUtility();
58
59 @BeforeClass
60 public static void setupCluster() throws Exception {
61
62 UTIL.startMiniDFSCluster(1);
63 CleanerChore.initChorePool(UTIL.getConfiguration());
64 }
65
66 @AfterClass
67 public static void shutdownCluster() throws IOException {
68 UTIL.shutdownMiniDFSCluster();
69 }
70
71 @Test
72 public void testTTLCleaner() throws IOException, InterruptedException {
73 FileSystem fs = UTIL.getDFSCluster().getFileSystem();
74 Path root = UTIL.getDataTestDirOnTestFS();
75 Path file = new Path(root, "file");
76 fs.createNewFile(file);
77 long createTime = System.currentTimeMillis();
78 assertTrue("Test file not created!", fs.exists(file));
79 TimeToLiveHFileCleaner cleaner = new TimeToLiveHFileCleaner();
80
81 fs.setTimes(file, createTime - 100, -1);
82 Configuration conf = UTIL.getConfiguration();
83 conf.setLong(TimeToLiveHFileCleaner.TTL_CONF_KEY, 100);
84 cleaner.setConf(conf);
85 assertTrue("File not set deletable - check mod time:" + getFileStats(file, fs)
86 + " with create time:" + createTime, cleaner.isFileDeletable(fs.getFileStatus(file)));
87 }
88
89
90
91
92
93 private String getFileStats(Path file, FileSystem fs) throws IOException {
94 FileStatus status = fs.getFileStatus(file);
95 return "File" + file + ", mtime:" + status.getModificationTime() + ", atime:"
96 + status.getAccessTime();
97 }
98
99 @Test(timeout = 60 *1000)
100 public void testHFileCleaning() throws Exception {
101 final EnvironmentEdge originalEdge = EnvironmentEdgeManager.getDelegate();
102 String prefix = "someHFileThatWouldBeAUUID";
103 Configuration conf = UTIL.getConfiguration();
104
105 long ttl = 2000;
106 conf.set(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS,
107 "org.apache.hadoop.hbase.master.cleaner.TimeToLiveHFileCleaner");
108 conf.setLong(TimeToLiveHFileCleaner.TTL_CONF_KEY, ttl);
109 Server server = new DummyServer();
110 Path archivedHfileDir = new Path(UTIL.getDataTestDirOnTestFS(), HConstants.HFILE_ARCHIVE_DIRECTORY);
111 FileSystem fs = FileSystem.get(conf);
112 HFileCleaner cleaner = new HFileCleaner(1000, server, conf, fs, archivedHfileDir);
113
114
115 final long createTime = System.currentTimeMillis();
116 fs.delete(archivedHfileDir, true);
117 fs.mkdirs(archivedHfileDir);
118
119 fs.createNewFile(new Path(archivedHfileDir, "dfd-dfd"));
120
121
122 LOG.debug("Now is: " + createTime);
123 for (int i = 1; i < 32; i++) {
124
125
126 Path fileName = new Path(archivedHfileDir, (prefix + "." + (createTime + i)));
127 fs.createNewFile(fileName);
128
129 fs.setTimes(fileName, createTime - ttl - 1, -1);
130 LOG.debug("Creating " + getFileStats(fileName, fs));
131 }
132
133
134
135 Path saved = new Path(archivedHfileDir, prefix + ".00000000000");
136 fs.createNewFile(saved);
137
138 fs.setTimes(saved, createTime - ttl / 2, -1);
139 LOG.debug("Creating " + getFileStats(saved, fs));
140 for (FileStatus stat : fs.listStatus(archivedHfileDir)) {
141 LOG.debug(stat.getPath().toString());
142 }
143
144 assertEquals(33, fs.listStatus(archivedHfileDir).length);
145
146
147 EnvironmentEdge setTime = new EnvironmentEdge() {
148 @Override
149 public long currentTime() {
150 return createTime;
151 }
152 };
153 EnvironmentEdgeManager.injectEdge(setTime);
154
155
156 cleaner.chore();
157
158
159 assertEquals(1, fs.listStatus(archivedHfileDir).length);
160
161 for (FileStatus file : fs.listStatus(archivedHfileDir)) {
162 LOG.debug("Kept hfiles: " + file.getPath().getName());
163 }
164
165
166 EnvironmentEdgeManager.injectEdge(originalEdge);
167 }
168
169 @Test
170 public void testRemovesEmptyDirectories() throws Exception {
171 Configuration conf = UTIL.getConfiguration();
172
173 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, "");
174 Server server = new DummyServer();
175 Path archivedHfileDir = new Path(UTIL.getDataTestDirOnTestFS(), HConstants.HFILE_ARCHIVE_DIRECTORY);
176
177
178 FileSystem fs = UTIL.getDFSCluster().getFileSystem();
179 HFileCleaner cleaner = new HFileCleaner(1000, server, conf, fs, archivedHfileDir);
180
181
182 Path table = new Path(archivedHfileDir, "table");
183 Path region = new Path(table, "regionsomthing");
184 Path family = new Path(region, "fam");
185 Path file = new Path(family, "file12345");
186 fs.mkdirs(family);
187 if (!fs.exists(family)) throw new RuntimeException("Couldn't create test family:" + family);
188 fs.create(file).close();
189 if (!fs.exists(file)) throw new RuntimeException("Test file didn't get created:" + file);
190
191
192 cleaner.chore();
193
194
195 assertFalse("family directory not removed for empty directory", fs.exists(family));
196 assertFalse("region directory not removed for empty directory", fs.exists(region));
197 assertFalse("table directory not removed for empty directory", fs.exists(table));
198 assertTrue("archive directory", fs.exists(archivedHfileDir));
199 }
200
201 static class DummyServer implements Server {
202
203 @Override
204 public Configuration getConfiguration() {
205 return UTIL.getConfiguration();
206 }
207
208 @Override
209 public ZooKeeperWatcher getZooKeeper() {
210 try {
211 return new ZooKeeperWatcher(getConfiguration(), "dummy server", this);
212 } catch (IOException e) {
213 e.printStackTrace();
214 }
215 return null;
216 }
217
218 @Override
219 public CoordinatedStateManager getCoordinatedStateManager() {
220 return null;
221 }
222
223 @Override
224 public ClusterConnection getConnection() {
225 return null;
226 }
227
228 @Override
229 public MetaTableLocator getMetaTableLocator() {
230 return null;
231 }
232
233 @Override
234 public ServerName getServerName() {
235 return ServerName.valueOf("regionserver,60020,000000");
236 }
237
238 @Override
239 public void abort(String why, Throwable e) {
240 }
241
242 @Override
243 public boolean isAborted() {
244 return false;
245 }
246
247 @Override
248 public void stop(String why) {
249 }
250
251 @Override
252 public boolean isStopped() {
253 return false;
254 }
255
256 @Override
257 public ChoreService getChoreService() {
258 return null;
259 }
260 }
261
262 @Test
263 public void testThreadCleanup() throws Exception {
264 Configuration conf = UTIL.getConfiguration();
265 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, "");
266 Server server = new DummyServer();
267 Path archivedHfileDir =
268 new Path(UTIL.getDataTestDirOnTestFS(), HConstants.HFILE_ARCHIVE_DIRECTORY);
269
270
271 FileSystem fs = UTIL.getDFSCluster().getFileSystem();
272 HFileCleaner cleaner = new HFileCleaner(1000, server, conf, fs, archivedHfileDir);
273
274 fs.delete(archivedHfileDir, true);
275 fs.mkdirs(archivedHfileDir);
276
277 fs.createNewFile(new Path(archivedHfileDir, "dfd-dfd"));
278
279 cleaner.chore();
280
281 cleaner.cleanup();
282
283 Thread.sleep(100);
284 for (Thread thread : cleaner.getCleanerThreads()) {
285 Assert.assertFalse(thread.isAlive());
286 }
287 }
288
289 @Test
290 public void testLargeSmallIsolation() throws Exception {
291 Configuration conf = UTIL.getConfiguration();
292
293 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, "");
294 conf.setInt(HFileCleaner.HFILE_DELETE_THROTTLE_THRESHOLD, 512 * 1024);
295 Server server = new DummyServer();
296 Path archivedHfileDir =
297 new Path(UTIL.getDataTestDirOnTestFS(), HConstants.HFILE_ARCHIVE_DIRECTORY);
298
299
300 FileSystem fs = UTIL.getDFSCluster().getFileSystem();
301 HFileCleaner cleaner = new HFileCleaner(1000, server, conf, fs, archivedHfileDir);
302
303 fs.delete(archivedHfileDir, true);
304 fs.mkdirs(archivedHfileDir);
305
306 final int LARGE_FILE_NUM = 5;
307 final int SMALL_FILE_NUM = 20;
308 createFilesForTesting(LARGE_FILE_NUM, SMALL_FILE_NUM, fs, archivedHfileDir);
309
310 cleaner.chore();
311
312 Assert.assertEquals(LARGE_FILE_NUM, cleaner.getNumOfDeletedLargeFiles());
313 Assert.assertEquals(SMALL_FILE_NUM, cleaner.getNumOfDeletedSmallFiles());
314 }
315
316 @Test(timeout = 60 * 1000)
317 public void testOnConfigurationChange() throws Exception {
318
319 final int ORIGINAL_THROTTLE_POINT = 512 * 1024;
320 final int ORIGINAL_QUEUE_INIT_SIZE = 512;
321 final int UPDATE_THROTTLE_POINT = 1024;
322 final int UPDATE_QUEUE_INIT_SIZE = 1024;
323 final int LARGE_FILE_NUM = 5;
324 final int SMALL_FILE_NUM = 20;
325 final int LARGE_THREAD_NUM = 2;
326 final int SMALL_THREAD_NUM = 4;
327 final long THREAD_TIMEOUT_MSEC = 30 * 1000L;
328 final long THREAD_CHECK_INTERVAL_MSEC = 500L;
329
330 Configuration conf = UTIL.getConfiguration();
331
332 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, "");
333 conf.setInt(HFileCleaner.HFILE_DELETE_THROTTLE_THRESHOLD, ORIGINAL_THROTTLE_POINT);
334 conf.setInt(HFileCleaner.LARGE_HFILE_QUEUE_INIT_SIZE, ORIGINAL_QUEUE_INIT_SIZE);
335 conf.setInt(HFileCleaner.SMALL_HFILE_QUEUE_INIT_SIZE, ORIGINAL_QUEUE_INIT_SIZE);
336 Server server = new DummyServer();
337 Path archivedHfileDir =
338 new Path(UTIL.getDataTestDirOnTestFS(), HConstants.HFILE_ARCHIVE_DIRECTORY);
339
340
341 FileSystem fs = UTIL.getDFSCluster().getFileSystem();
342 final HFileCleaner cleaner = new HFileCleaner(1000, server, conf, fs, archivedHfileDir);
343 Assert.assertEquals(ORIGINAL_THROTTLE_POINT, cleaner.getThrottlePoint());
344 Assert.assertEquals(ORIGINAL_QUEUE_INIT_SIZE, cleaner.getLargeQueueInitSize());
345 Assert.assertEquals(ORIGINAL_QUEUE_INIT_SIZE, cleaner.getSmallQueueInitSize());
346 Assert.assertEquals(HFileCleaner.DEFAULT_HFILE_DELETE_THREAD_TIMEOUT_MSEC,
347 cleaner.getCleanerThreadTimeoutMsec());
348 Assert.assertEquals(HFileCleaner.DEFAULT_HFILE_DELETE_THREAD_CHECK_INTERVAL_MSEC,
349 cleaner.getCleanerThreadCheckIntervalMsec());
350
351
352 fs.delete(archivedHfileDir, true);
353 fs.mkdirs(archivedHfileDir);
354 createFilesForTesting(LARGE_FILE_NUM, SMALL_FILE_NUM, fs, archivedHfileDir);
355
356
357 Thread t = new Thread() {
358 @Override
359 public void run() {
360 cleaner.chore();
361 }
362 };
363 t.setDaemon(true);
364 t.start();
365
366 while (cleaner.getNumOfDeletedSmallFiles() == 0) {
367 Thread.yield();
368 }
369
370
371 Configuration newConf = new Configuration(conf);
372 newConf.setInt(HFileCleaner.HFILE_DELETE_THROTTLE_THRESHOLD, UPDATE_THROTTLE_POINT);
373 newConf.setInt(HFileCleaner.LARGE_HFILE_QUEUE_INIT_SIZE, UPDATE_QUEUE_INIT_SIZE);
374 newConf.setInt(HFileCleaner.SMALL_HFILE_QUEUE_INIT_SIZE, UPDATE_QUEUE_INIT_SIZE);
375 newConf.setInt(HFileCleaner.LARGE_HFILE_DELETE_THREAD_NUMBER, LARGE_THREAD_NUM);
376 newConf.setInt(HFileCleaner.SMALL_HFILE_DELETE_THREAD_NUMBER, SMALL_THREAD_NUM);
377 newConf.setLong(HFileCleaner.HFILE_DELETE_THREAD_TIMEOUT_MSEC, THREAD_TIMEOUT_MSEC);
378 newConf.setLong(HFileCleaner.HFILE_DELETE_THREAD_CHECK_INTERVAL_MSEC,
379 THREAD_CHECK_INTERVAL_MSEC);
380 cleaner.onConfigurationChange(newConf);
381
382 LOG.debug("File deleted from large queue: " + cleaner.getNumOfDeletedLargeFiles()
383 + "; from small queue: " + cleaner.getNumOfDeletedSmallFiles());
384 cleaner.onConfigurationChange(newConf);
385
386
387 Assert.assertEquals(UPDATE_THROTTLE_POINT, cleaner.getThrottlePoint());
388 Assert.assertEquals(UPDATE_QUEUE_INIT_SIZE, cleaner.getLargeQueueInitSize());
389 Assert.assertEquals(UPDATE_QUEUE_INIT_SIZE, cleaner.getSmallQueueInitSize());
390 Assert.assertEquals(LARGE_THREAD_NUM + SMALL_THREAD_NUM, cleaner.getCleanerThreads().size());
391 Assert.assertEquals(THREAD_TIMEOUT_MSEC, cleaner.getCleanerThreadTimeoutMsec());
392 Assert.assertEquals(THREAD_CHECK_INTERVAL_MSEC, cleaner.getCleanerThreadCheckIntervalMsec());
393
394
395 List<Thread> oldThreads = cleaner.getCleanerThreads();
396 cleaner.onConfigurationChange(newConf);
397 List<Thread> newThreads = cleaner.getCleanerThreads();
398 Assert.assertArrayEquals(oldThreads.toArray(), newThreads.toArray());
399
400
401 t.join();
402 LOG.debug("File deleted from large queue: " + cleaner.getNumOfDeletedLargeFiles()
403 + "; from small queue: " + cleaner.getNumOfDeletedSmallFiles());
404 Assert.assertTrue("Should delete more than " + LARGE_FILE_NUM
405 + " files from large queue but actually " + cleaner.getNumOfDeletedLargeFiles(),
406 cleaner.getNumOfDeletedLargeFiles() > LARGE_FILE_NUM);
407 Assert.assertTrue("Should delete less than " + SMALL_FILE_NUM
408 + " files from small queue but actually " + cleaner.getNumOfDeletedSmallFiles(),
409 cleaner.getNumOfDeletedSmallFiles() < SMALL_FILE_NUM);
410 }
411
412 private void createFilesForTesting(int largeFileNum, int smallFileNum, FileSystem fs,
413 Path archivedHfileDir) throws IOException {
414 final Random rand = new Random();
415 final byte[] large = new byte[1024 * 1024];
416 for (int i = 0; i < large.length; i++) {
417 large[i] = (byte) rand.nextInt(128);
418 }
419 final byte[] small = new byte[1024];
420 for (int i = 0; i < small.length; i++) {
421 small[i] = (byte) rand.nextInt(128);
422 }
423
424 for (int i = 1; i <= largeFileNum; i++) {
425 FSDataOutputStream out = fs.create(new Path(archivedHfileDir, "large-file-" + i));
426 out.write(large);
427 out.close();
428 }
429 for (int i = 1; i <= smallFileNum; i++) {
430 FSDataOutputStream out = fs.create(new Path(archivedHfileDir, "small-file-" + i));
431 out.write(small);
432 out.close();
433 }
434 }
435 }