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}