001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.master.cleaner; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023 024import java.io.IOException; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FileStatus; 027import org.apache.hadoop.fs.FileSystem; 028import org.apache.hadoop.fs.Path; 029import org.apache.hadoop.hbase.ChoreService; 030import org.apache.hadoop.hbase.CoordinatedStateManager; 031import org.apache.hadoop.hbase.HBaseClassTestRule; 032import org.apache.hadoop.hbase.HBaseTestingUtility; 033import org.apache.hadoop.hbase.Server; 034import org.apache.hadoop.hbase.ServerName; 035import org.apache.hadoop.hbase.TableName; 036import org.apache.hadoop.hbase.client.ClusterConnection; 037import org.apache.hadoop.hbase.client.Connection; 038import org.apache.hadoop.hbase.client.RegionInfo; 039import org.apache.hadoop.hbase.client.RegionInfoBuilder; 040import org.apache.hadoop.hbase.io.HFileLink; 041import org.apache.hadoop.hbase.testclassification.MasterTests; 042import org.apache.hadoop.hbase.testclassification.MediumTests; 043import org.apache.hadoop.hbase.util.CommonFSUtils; 044import org.apache.hadoop.hbase.util.HFileArchiveUtil; 045import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 046import org.junit.After; 047import org.junit.AfterClass; 048import org.junit.Before; 049import org.junit.BeforeClass; 050import org.junit.ClassRule; 051import org.junit.Rule; 052import org.junit.Test; 053import org.junit.experimental.categories.Category; 054import org.junit.rules.TestName; 055 056/** 057 * Test the HFileLink Cleaner. HFiles with links cannot be deleted until a link is present. 058 */ 059@Category({ MasterTests.class, MediumTests.class }) 060public class TestHFileLinkCleaner { 061 062 @ClassRule 063 public static final HBaseClassTestRule CLASS_RULE = 064 HBaseClassTestRule.forClass(TestHFileLinkCleaner.class); 065 066 private Configuration conf; 067 private Path rootDir; 068 private FileSystem fs; 069 private TableName tableName; 070 private TableName tableLinkName; 071 private String hfileName; 072 private String familyName; 073 private RegionInfo hri; 074 private RegionInfo hriLink; 075 private Path archiveDir; 076 private Path archiveStoreDir; 077 private Path familyPath; 078 private Path hfilePath; 079 private Path familyLinkPath; 080 private String hfileLinkName; 081 private Path linkBackRefDir; 082 private Path linkBackRef; 083 private FileStatus[] backRefs; 084 private HFileCleaner cleaner; 085 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 086 private static DirScanPool POOL; 087 private static final long TTL = 1000; 088 089 @Rule 090 public TestName name = new TestName(); 091 092 @BeforeClass 093 public static void setUp() { 094 POOL = DirScanPool.getHFileCleanerScanPool(TEST_UTIL.getConfiguration()); 095 } 096 097 @AfterClass 098 public static void tearDown() { 099 POOL.shutdownNow(); 100 } 101 102 @Before 103 public void configureDirectoriesAndLinks() throws IOException { 104 conf = TEST_UTIL.getConfiguration(); 105 CommonFSUtils.setRootDir(conf, TEST_UTIL.getDataTestDir()); 106 conf.set(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, HFileLinkCleaner.class.getName()); 107 rootDir = CommonFSUtils.getRootDir(conf); 108 fs = FileSystem.get(conf); 109 110 tableName = TableName.valueOf(name.getMethodName()); 111 tableLinkName = TableName.valueOf(name.getMethodName() + "-link"); 112 hfileName = "1234567890"; 113 familyName = "cf"; 114 115 hri = RegionInfoBuilder.newBuilder(tableName).build(); 116 hriLink = RegionInfoBuilder.newBuilder(tableLinkName).build(); 117 118 archiveDir = HFileArchiveUtil.getArchivePath(conf); 119 archiveStoreDir = 120 HFileArchiveUtil.getStoreArchivePath(conf, tableName, hri.getEncodedName(), familyName); 121 122 // Create hfile /hbase/table-link/region/cf/getEncodedName.HFILE(conf); 123 familyPath = getFamilyDirPath(archiveDir, tableName, hri.getEncodedName(), familyName); 124 fs.mkdirs(familyPath); 125 hfilePath = new Path(familyPath, hfileName); 126 fs.createNewFile(hfilePath); 127 128 createLink(true); 129 130 // Initialize cleaner 131 conf.setLong(TimeToLiveHFileCleaner.TTL_CONF_KEY, TTL); 132 Server server = new DummyServer(); 133 cleaner = new HFileCleaner(1000, server, conf, fs, archiveDir, POOL); 134 } 135 136 private void createLink(boolean createBackReference) throws IOException { 137 // Create link to hfile 138 familyLinkPath = getFamilyDirPath(rootDir, tableLinkName, hriLink.getEncodedName(), familyName); 139 fs.mkdirs(familyLinkPath); 140 hfileLinkName = HFileLink.create(conf, fs, familyLinkPath, hri, hfileName, createBackReference); 141 linkBackRefDir = HFileLink.getBackReferencesDir(archiveStoreDir, hfileName); 142 assertTrue(fs.exists(linkBackRefDir)); 143 backRefs = fs.listStatus(linkBackRefDir); 144 assertEquals(1, backRefs.length); 145 linkBackRef = backRefs[0].getPath(); 146 } 147 148 @After 149 public void cleanup() throws IOException, InterruptedException { 150 // HFile can be removed 151 Thread.sleep(TTL * 2); 152 cleaner.chore(); 153 assertFalse("HFile should be deleted", fs.exists(hfilePath)); 154 // Remove everything 155 for (int i = 0; i < 4; ++i) { 156 Thread.sleep(TTL * 2); 157 cleaner.chore(); 158 } 159 assertFalse("HFile should be deleted", 160 fs.exists(CommonFSUtils.getTableDir(archiveDir, tableName))); 161 assertFalse("Link should be deleted", 162 fs.exists(CommonFSUtils.getTableDir(archiveDir, tableLinkName))); 163 } 164 165 @Test 166 public void testHFileLinkCleaning() throws Exception { 167 // Link backref cannot be removed 168 cleaner.chore(); 169 assertTrue(fs.exists(linkBackRef)); 170 assertTrue(fs.exists(hfilePath)); 171 172 // Link backref can be removed 173 fs.rename(CommonFSUtils.getTableDir(rootDir, tableLinkName), 174 CommonFSUtils.getTableDir(archiveDir, tableLinkName)); 175 cleaner.chore(); 176 assertFalse("Link should be deleted", fs.exists(linkBackRef)); 177 } 178 179 @Test 180 public void testHFileLinkByRemovingReference() throws Exception { 181 // Link backref cannot be removed 182 cleaner.chore(); 183 assertTrue(fs.exists(linkBackRef)); 184 assertTrue(fs.exists(hfilePath)); 185 186 // simulate after removing the reference in data directory, the Link backref can be removed 187 fs.delete(new Path(familyLinkPath, hfileLinkName), false); 188 cleaner.chore(); 189 assertFalse("Link should be deleted", fs.exists(linkBackRef)); 190 } 191 192 @Test 193 public void testHFileLinkEmptyBackReferenceDirectory() throws Exception { 194 // simulate and remove the back reference 195 fs.delete(linkBackRef, false); 196 assertTrue("back reference directory still exists", fs.exists(linkBackRefDir)); 197 cleaner.chore(); 198 assertFalse("back reference directory should be deleted", fs.exists(linkBackRefDir)); 199 } 200 201 private static Path getFamilyDirPath(final Path rootDir, final TableName table, 202 final String region, final String family) { 203 return new Path(new Path(CommonFSUtils.getTableDir(rootDir, table), region), family); 204 } 205 206 static class DummyServer implements Server { 207 208 @Override 209 public Configuration getConfiguration() { 210 return TEST_UTIL.getConfiguration(); 211 } 212 213 @Override 214 public ZKWatcher getZooKeeper() { 215 try { 216 return new ZKWatcher(getConfiguration(), "dummy server", this); 217 } catch (IOException e) { 218 e.printStackTrace(); 219 } 220 return null; 221 } 222 223 @Override 224 public CoordinatedStateManager getCoordinatedStateManager() { 225 return null; 226 } 227 228 @Override 229 public ClusterConnection getConnection() { 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 @Override 262 public ClusterConnection getClusterConnection() { 263 // TODO Auto-generated method stub 264 return null; 265 } 266 267 @Override 268 public FileSystem getFileSystem() { 269 return null; 270 } 271 272 @Override 273 public boolean isStopping() { 274 return false; 275 } 276 277 @Override 278 public Connection createConnection(Configuration conf) throws IOException { 279 return null; 280 } 281 } 282}