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