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.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtil; 031import org.apache.hadoop.hbase.Server; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.RegionInfo; 034import org.apache.hadoop.hbase.client.RegionInfoBuilder; 035import org.apache.hadoop.hbase.io.HFileLink; 036import org.apache.hadoop.hbase.testclassification.MasterTests; 037import org.apache.hadoop.hbase.testclassification.MediumTests; 038import org.apache.hadoop.hbase.util.CommonFSUtils; 039import org.apache.hadoop.hbase.util.HFileArchiveUtil; 040import org.apache.hadoop.hbase.util.MockServer; 041import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 042import org.junit.After; 043import org.junit.AfterClass; 044import org.junit.Before; 045import org.junit.BeforeClass; 046import org.junit.ClassRule; 047import org.junit.Rule; 048import org.junit.Test; 049import org.junit.experimental.categories.Category; 050import org.junit.rules.TestName; 051 052/** 053 * Test the HFileLink Cleaner. HFiles with links cannot be deleted until a link is present. 054 */ 055@Category({ MasterTests.class, MediumTests.class }) 056public class TestHFileLinkCleaner { 057 058 @ClassRule 059 public static final HBaseClassTestRule CLASS_RULE = 060 HBaseClassTestRule.forClass(TestHFileLinkCleaner.class); 061 062 private Configuration conf; 063 private Path rootDir; 064 private FileSystem fs; 065 private TableName tableName; 066 private TableName tableLinkName; 067 private String hfileName; 068 private String familyName; 069 private RegionInfo hri; 070 private RegionInfo hriLink; 071 private Path archiveDir; 072 private Path archiveStoreDir; 073 private Path familyPath; 074 private Path hfilePath; 075 private Path familyLinkPath; 076 private String hfileLinkName; 077 private Path linkBackRefDir; 078 private Path linkBackRef; 079 private FileStatus[] backRefs; 080 private HFileCleaner cleaner; 081 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 082 private static DirScanPool POOL; 083 private static final long TTL = 1000; 084 085 @Rule 086 public TestName name = new TestName(); 087 088 @BeforeClass 089 public static void setUp() { 090 POOL = DirScanPool.getHFileCleanerScanPool(TEST_UTIL.getConfiguration()); 091 } 092 093 @AfterClass 094 public static void tearDown() { 095 POOL.shutdownNow(); 096 } 097 098 @Before 099 public void configureDirectoriesAndLinks() throws IOException { 100 conf = TEST_UTIL.getConfiguration(); 101 CommonFSUtils.setRootDir(conf, TEST_UTIL.getDataTestDir()); 102 conf.set(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, HFileLinkCleaner.class.getName()); 103 rootDir = CommonFSUtils.getRootDir(conf); 104 fs = FileSystem.get(conf); 105 106 tableName = TableName.valueOf(name.getMethodName()); 107 tableLinkName = TableName.valueOf(name.getMethodName() + "-link"); 108 hfileName = "1234567890"; 109 familyName = "cf"; 110 111 hri = RegionInfoBuilder.newBuilder(tableName).build(); 112 hriLink = RegionInfoBuilder.newBuilder(tableLinkName).build(); 113 114 archiveDir = HFileArchiveUtil.getArchivePath(conf); 115 archiveStoreDir = 116 HFileArchiveUtil.getStoreArchivePath(conf, tableName, hri.getEncodedName(), familyName); 117 118 // Create hfile /hbase/table-link/region/cf/getEncodedName.HFILE(conf); 119 familyPath = getFamilyDirPath(archiveDir, tableName, hri.getEncodedName(), familyName); 120 fs.mkdirs(familyPath); 121 hfilePath = new Path(familyPath, hfileName); 122 fs.createNewFile(hfilePath); 123 124 createLink(true); 125 126 // Initialize cleaner 127 conf.setLong(TimeToLiveHFileCleaner.TTL_CONF_KEY, TTL); 128 Server server = new DummyServer(); 129 cleaner = new HFileCleaner(1000, server, conf, fs, archiveDir, POOL); 130 } 131 132 private void createLink(boolean createBackReference) throws IOException { 133 // Create link to hfile 134 familyLinkPath = getFamilyDirPath(rootDir, tableLinkName, hriLink.getEncodedName(), familyName); 135 fs.mkdirs(familyLinkPath); 136 hfileLinkName = HFileLink.create(conf, fs, familyLinkPath, hri, hfileName, createBackReference); 137 linkBackRefDir = HFileLink.getBackReferencesDir(archiveStoreDir, hfileName); 138 assertTrue(fs.exists(linkBackRefDir)); 139 backRefs = fs.listStatus(linkBackRefDir); 140 assertEquals(1, backRefs.length); 141 linkBackRef = backRefs[0].getPath(); 142 } 143 144 @After 145 public void cleanup() throws IOException, InterruptedException { 146 // HFile can be removed 147 Thread.sleep(TTL * 2); 148 cleaner.chore(); 149 assertFalse("HFile should be deleted", fs.exists(hfilePath)); 150 // Remove everything 151 for (int i = 0; i < 4; ++i) { 152 Thread.sleep(TTL * 2); 153 cleaner.chore(); 154 } 155 assertFalse("HFile should be deleted", 156 fs.exists(CommonFSUtils.getTableDir(archiveDir, tableName))); 157 assertFalse("Link should be deleted", 158 fs.exists(CommonFSUtils.getTableDir(archiveDir, tableLinkName))); 159 } 160 161 @Test 162 public void testHFileLinkCleaning() throws Exception { 163 // Link backref cannot be removed 164 cleaner.chore(); 165 assertTrue(fs.exists(linkBackRef)); 166 assertTrue(fs.exists(hfilePath)); 167 168 // Link backref can be removed 169 fs.rename(CommonFSUtils.getTableDir(rootDir, tableLinkName), 170 CommonFSUtils.getTableDir(archiveDir, tableLinkName)); 171 cleaner.chore(); 172 assertFalse("Link should be deleted", fs.exists(linkBackRef)); 173 } 174 175 @Test 176 public void testHFileLinkByRemovingReference() throws Exception { 177 // Link backref cannot be removed 178 cleaner.chore(); 179 assertTrue(fs.exists(linkBackRef)); 180 assertTrue(fs.exists(hfilePath)); 181 182 // simulate after removing the reference in data directory, the Link backref can be removed 183 fs.delete(new Path(familyLinkPath, hfileLinkName), false); 184 cleaner.chore(); 185 assertFalse("Link should be deleted", fs.exists(linkBackRef)); 186 } 187 188 @Test 189 public void testHFileLinkEmptyBackReferenceDirectory() throws Exception { 190 // simulate and remove the back reference 191 fs.delete(linkBackRef, false); 192 assertTrue("back reference directory still exists", fs.exists(linkBackRefDir)); 193 cleaner.chore(); 194 assertFalse("back reference directory should be deleted", fs.exists(linkBackRefDir)); 195 } 196 197 private static Path getFamilyDirPath(final Path rootDir, final TableName table, 198 final String region, final String family) { 199 return new Path(new Path(CommonFSUtils.getTableDir(rootDir, table), region), family); 200 } 201 202 static class DummyServer extends MockServer { 203 204 @Override 205 public Configuration getConfiguration() { 206 return TEST_UTIL.getConfiguration(); 207 } 208 209 @Override 210 public ZKWatcher getZooKeeper() { 211 try { 212 return new ZKWatcher(getConfiguration(), "dummy server", this); 213 } catch (IOException e) { 214 e.printStackTrace(); 215 } 216 return null; 217 } 218 } 219}