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.regionserver; 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; 023import static org.mockito.Mockito.mock; 024import static org.mockito.Mockito.when; 025 026import java.io.IOException; 027import java.util.ArrayList; 028import java.util.List; 029import org.apache.hadoop.fs.FSDataOutputStream; 030import org.apache.hadoop.fs.Path; 031import org.apache.hadoop.hbase.HBaseTestingUtil; 032import org.apache.hadoop.hbase.ServerName; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.client.Put; 035import org.apache.hadoop.hbase.client.Table; 036import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 037import org.apache.hadoop.hbase.testclassification.MediumTests; 038import org.apache.hadoop.hbase.testclassification.RegionServerTests; 039import org.apache.hadoop.hbase.util.Bytes; 040import org.junit.jupiter.api.AfterEach; 041import org.junit.jupiter.api.BeforeEach; 042import org.junit.jupiter.api.Tag; 043import org.junit.jupiter.api.Test; 044 045@Tag(MediumTests.TAG) 046@Tag(RegionServerTests.TAG) 047public class TestBrokenStoreFileCleaner { 048 049 private final HBaseTestingUtil testUtil = new HBaseTestingUtil(); 050 private final static byte[] fam = Bytes.toBytes("cf_1"); 051 private final static byte[] qual1 = Bytes.toBytes("qf_1"); 052 private final static byte[] val = Bytes.toBytes("val"); 053 private final static String junkFileName = "409fad9a751c4e8c86d7f32581bdc156"; 054 TableName tableName; 055 056 @BeforeEach 057 public void setUp() throws Exception { 058 testUtil.getConfiguration().set(StoreFileTrackerFactory.TRACKER_IMPL, 059 "org.apache.hadoop.hbase.regionserver.storefiletracker.FileBasedStoreFileTracker"); 060 testUtil.getConfiguration().set(BrokenStoreFileCleaner.BROKEN_STOREFILE_CLEANER_ENABLED, 061 "true"); 062 testUtil.getConfiguration().set(BrokenStoreFileCleaner.BROKEN_STOREFILE_CLEANER_TTL, "0"); 063 testUtil.getConfiguration().set(BrokenStoreFileCleaner.BROKEN_STOREFILE_CLEANER_PERIOD, 064 "15000000"); 065 testUtil.getConfiguration().set(BrokenStoreFileCleaner.BROKEN_STOREFILE_CLEANER_DELAY, "0"); 066 testUtil.startMiniCluster(1); 067 } 068 069 @AfterEach 070 public void tearDown() throws Exception { 071 testUtil.shutdownMiniCluster(); 072 } 073 074 @Test 075 public void testDeletingJunkFile() throws Exception { 076 tableName = TableName.valueOf(getClass().getSimpleName() + "testDeletingJunkFile"); 077 createTableWithData(tableName); 078 079 HRegion region = testUtil.getMiniHBaseCluster().getRegions(tableName).get(0); 080 ServerName sn = testUtil.getMiniHBaseCluster().getServerHoldingRegion(tableName, 081 region.getRegionInfo().getRegionName()); 082 HRegionServer rs = testUtil.getMiniHBaseCluster().getRegionServer(sn); 083 BrokenStoreFileCleaner cleaner = rs.getBrokenStoreFileCleaner(); 084 085 // create junk file 086 HStore store = region.getStore(fam); 087 Path cfPath = store.getRegionFileSystem().getStoreDir(store.getColumnFamilyName()); 088 Path junkFilePath = new Path(cfPath, junkFileName); 089 090 FSDataOutputStream junkFileOS = store.getFileSystem().create(junkFilePath); 091 junkFileOS.writeUTF("hello"); 092 junkFileOS.close(); 093 094 int storeFiles = store.getStorefilesCount(); 095 assertTrue(storeFiles > 0); 096 097 // verify the file exist before the chore and missing afterwards 098 assertTrue(store.getFileSystem().exists(junkFilePath)); 099 cleaner.chore(); 100 assertFalse(store.getFileSystem().exists(junkFilePath)); 101 102 // verify no storefile got deleted 103 int currentStoreFiles = store.getStorefilesCount(); 104 assertEquals(currentStoreFiles, storeFiles); 105 } 106 107 @Test 108 public void testSkippingCompactedFiles() throws Exception { 109 tableName = TableName.valueOf(getClass().getSimpleName() + "testSkippningCompactedFiles"); 110 createTableWithData(tableName); 111 112 HRegion region = testUtil.getMiniHBaseCluster().getRegions(tableName).get(0); 113 114 ServerName sn = testUtil.getMiniHBaseCluster().getServerHoldingRegion(tableName, 115 region.getRegionInfo().getRegionName()); 116 HRegionServer rs = testUtil.getMiniHBaseCluster().getRegionServer(sn); 117 BrokenStoreFileCleaner cleaner = rs.getBrokenStoreFileCleaner(); 118 119 // run major compaction to generate compaced files 120 region.compact(true); 121 122 // make sure there are compacted files 123 HStore store = region.getStore(fam); 124 int compactedFiles = store.getCompactedFilesCount(); 125 assertTrue(compactedFiles > 0); 126 127 cleaner.chore(); 128 129 // verify none of the compacted files were deleted 130 int existingCompactedFiles = store.getCompactedFilesCount(); 131 assertEquals(compactedFiles, existingCompactedFiles); 132 133 // verify adding a junk file does not break anything 134 Path cfPath = store.getRegionFileSystem().getStoreDir(store.getColumnFamilyName()); 135 Path junkFilePath = new Path(cfPath, junkFileName); 136 137 FSDataOutputStream junkFileOS = store.getFileSystem().create(junkFilePath); 138 junkFileOS.writeUTF("hello"); 139 junkFileOS.close(); 140 141 assertTrue(store.getFileSystem().exists(junkFilePath)); 142 cleaner.setEnabled(true); 143 cleaner.chore(); 144 assertFalse(store.getFileSystem().exists(junkFilePath)); 145 146 // verify compacted files are still intact 147 existingCompactedFiles = store.getCompactedFilesCount(); 148 assertEquals(compactedFiles, existingCompactedFiles); 149 } 150 151 @Test 152 public void testJunkFileTTL() throws Exception { 153 tableName = TableName.valueOf(getClass().getSimpleName() + "testDeletingJunkFile"); 154 createTableWithData(tableName); 155 156 HRegion region = testUtil.getMiniHBaseCluster().getRegions(tableName).get(0); 157 ServerName sn = testUtil.getMiniHBaseCluster().getServerHoldingRegion(tableName, 158 region.getRegionInfo().getRegionName()); 159 HRegionServer rs = testUtil.getMiniHBaseCluster().getRegionServer(sn); 160 161 // create junk file 162 HStore store = region.getStore(fam); 163 Path cfPath = store.getRegionFileSystem().getStoreDir(store.getColumnFamilyName()); 164 Path junkFilePath = new Path(cfPath, junkFileName); 165 166 FSDataOutputStream junkFileOS = store.getFileSystem().create(junkFilePath); 167 junkFileOS.writeUTF("hello"); 168 junkFileOS.close(); 169 170 int storeFiles = store.getStorefilesCount(); 171 assertTrue(storeFiles > 0); 172 173 // verify the file exist before the chore 174 assertTrue(store.getFileSystem().exists(junkFilePath)); 175 176 // set a 5 sec ttl 177 rs.getConfiguration().set(BrokenStoreFileCleaner.BROKEN_STOREFILE_CLEANER_TTL, "5000"); 178 BrokenStoreFileCleaner cleaner = 179 new BrokenStoreFileCleaner(15000000, 0, rs, rs.getConfiguration(), rs); 180 cleaner.chore(); 181 // file is still present after chore run 182 assertTrue(store.getFileSystem().exists(junkFilePath)); 183 Thread.sleep(5000); 184 cleaner.chore(); 185 assertFalse(store.getFileSystem().exists(junkFilePath)); 186 187 // verify no storefile got deleted 188 int currentStoreFiles = store.getStorefilesCount(); 189 assertEquals(currentStoreFiles, storeFiles); 190 } 191 192 @Test 193 public void testWhenRegionIsClosing() throws Exception { 194 tableName = TableName.valueOf(getClass().getSimpleName() + "testWhenRegionIsClosing"); 195 createTableWithData(tableName); 196 197 HRegion region = testUtil.getMiniHBaseCluster().getRegions(tableName).get(0); 198 ServerName sn = testUtil.getMiniHBaseCluster().getServerHoldingRegion(tableName, 199 region.getRegionInfo().getRegionName()); 200 HRegionServer rs = testUtil.getMiniHBaseCluster().getRegionServer(sn); 201 202 HStore store = region.getStore(fam); 203 int expectedStoreFiles = store.getStorefilesCount(); 204 assertTrue(expectedStoreFiles > 0); 205 Path cfPath = store.getRegionFileSystem().getStoreDir(store.getColumnFamilyName()); 206 // because we use FILE SFT, there will be a .filelist dir under the store dir 207 int totalFiles = store.getRegionFileSystem().getFileSystem().listStatus(cfPath).length - 1; 208 assertEquals(expectedStoreFiles, totalFiles); 209 210 HRegionServer mockedServer = mock(HRegionServer.class); 211 HRegion mockedRegion = mock(HRegion.class); 212 when(mockedRegion.isAvailable()).thenReturn(region.isAvailable()); 213 when(mockedRegion.getRegionFileSystem()).thenReturn(region.getRegionFileSystem()); 214 List<HRegion> mockedRegionsList = new ArrayList<>(); 215 mockedRegionsList.add(mockedRegion); 216 when(mockedServer.getRegions()).thenReturn(mockedRegionsList); 217 when(mockedServer.getServerName()).thenReturn(rs.getServerName()); 218 when(mockedRegion.getStores()).thenAnswer(i -> { 219 region.close(); 220 return region.getStores(); 221 }); 222 223 BrokenStoreFileCleaner cleaner = 224 new BrokenStoreFileCleaner(15000000, 0, rs, rs.getConfiguration(), mockedServer); 225 226 cleaner.chore(); 227 228 // verify no storefile got deleted 229 int currentStoreFiles = 230 store.getRegionFileSystem().getFileSystem().listStatus(cfPath).length - 1; 231 assertEquals(expectedStoreFiles, currentStoreFiles); 232 } 233 234 private Table createTableWithData(TableName tableName) throws IOException { 235 Table table = testUtil.createTable(tableName, fam); 236 try { 237 for (int i = 1; i < 10; i++) { 238 Put p = new Put(Bytes.toBytes("row" + i)); 239 p.addColumn(fam, qual1, val); 240 table.put(p); 241 } 242 // flush them 243 testUtil.getAdmin().flush(tableName); 244 for (int i = 11; i < 20; i++) { 245 Put p = new Put(Bytes.toBytes("row" + i)); 246 p.addColumn(fam, qual1, val); 247 table.put(p); 248 } 249 // flush them 250 testUtil.getAdmin().flush(tableName); 251 for (int i = 21; i < 30; i++) { 252 Put p = new Put(Bytes.toBytes("row" + i)); 253 p.addColumn(fam, qual1, val); 254 table.put(p); 255 } 256 // flush them 257 testUtil.getAdmin().flush(tableName); 258 } catch (IOException e) { 259 table.close(); 260 throw e; 261 } 262 return table; 263 } 264}