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.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertTrue;
024import static org.mockito.ArgumentMatchers.any;
025import static org.mockito.ArgumentMatchers.eq;
026import static org.mockito.Mockito.doThrow;
027import static org.mockito.Mockito.mock;
028import static org.mockito.Mockito.spy;
029
030import java.io.IOException;
031import java.util.ArrayList;
032import java.util.Collection;
033import java.util.List;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.fs.FSDataOutputStream;
036import org.apache.hadoop.fs.FileSystem;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.hbase.HBaseClassTestRule;
039import org.apache.hadoop.hbase.HBaseTestingUtil;
040import org.apache.hadoop.hbase.Stoppable;
041import org.apache.hadoop.hbase.TableName;
042import org.apache.hadoop.hbase.backup.FailedArchiveException;
043import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
044import org.apache.hadoop.hbase.client.Put;
045import org.apache.hadoop.hbase.client.RegionInfo;
046import org.apache.hadoop.hbase.client.RegionInfoBuilder;
047import org.apache.hadoop.hbase.client.TableDescriptor;
048import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
049import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerForTest;
050import org.apache.hadoop.hbase.testclassification.MediumTests;
051import org.apache.hadoop.hbase.util.Bytes;
052import org.apache.hadoop.hbase.util.CommonFSUtils;
053import org.apache.hadoop.hbase.wal.WALFactory;
054import org.junit.After;
055import org.junit.Before;
056import org.junit.ClassRule;
057import org.junit.Rule;
058import org.junit.Test;
059import org.junit.experimental.categories.Category;
060import org.junit.rules.TestName;
061import org.mockito.Mockito;
062
063import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
064
065/**
066 * Tests that archiving compacted files behaves correctly when encountering exceptions.
067 */
068@Category(MediumTests.class)
069public class TestCompactionArchiveIOException {
070
071  @ClassRule
072  public static final HBaseClassTestRule CLASS_RULE =
073    HBaseClassTestRule.forClass(TestCompactionArchiveIOException.class);
074
075  private static final String ERROR_FILE = "fffffffffffffffffdeadbeef";
076
077  public HBaseTestingUtil testUtil;
078
079  private Path testDir;
080
081  @Rule
082  public TestName name = new TestName();
083
084  @Before
085  public void setup() throws Exception {
086    testUtil = new HBaseTestingUtil();
087    testUtil.startMiniDFSCluster(1);
088    testDir = testUtil.getDataTestDirOnTestFS();
089    CommonFSUtils.setRootDir(testUtil.getConfiguration(), testDir);
090  }
091
092  @After
093  public void tearDown() throws Exception {
094    testUtil.cleanupTestDir();
095    testUtil.shutdownMiniDFSCluster();
096  }
097
098  @Test
099  public void testRemoveCompactedFilesWithException() throws Exception {
100    byte[] fam = Bytes.toBytes("f");
101    byte[] col = Bytes.toBytes("c");
102    byte[] val = Bytes.toBytes("val");
103
104    TableName tableName = TableName.valueOf(name.getMethodName());
105    TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName)
106      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(fam)).build();
107    RegionInfo info = RegionInfoBuilder.newBuilder(tableName).build();
108    HRegion region = initHRegion(htd, info);
109    RegionServerServices rss = mock(RegionServerServices.class);
110    List<HRegion> regions = new ArrayList<>();
111    regions.add(region);
112    Mockito.doReturn(regions).when(rss).getRegions();
113
114    // Create the cleaner object
115    final CompactedHFilesDischarger cleaner =
116      new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
117    // Add some data to the region and do some flushes
118    int batchSize = 10;
119    int fileCount = 10;
120    for (int f = 0; f < fileCount; f++) {
121      int start = f * batchSize;
122      for (int i = start; i < start + batchSize; i++) {
123        Put p = new Put(Bytes.toBytes("row" + i));
124        p.addColumn(fam, col, val);
125        region.put(p);
126      }
127      // flush them
128      region.flush(true);
129    }
130
131    HStore store = region.getStore(fam);
132    assertEquals(fileCount, store.getStorefilesCount());
133
134    Collection<HStoreFile> storefiles = store.getStorefiles();
135    // None of the files should be in compacted state.
136    for (HStoreFile file : storefiles) {
137      assertFalse(file.isCompactedAway());
138    }
139
140    StoreFileManager fileManager = store.getStoreEngine().getStoreFileManager();
141    Collection<HStoreFile> initialCompactedFiles = fileManager.getCompactedfiles();
142    assertTrue(initialCompactedFiles == null || initialCompactedFiles.isEmpty());
143
144    // Do compaction
145    region.compact(true);
146
147    // all prior store files should now be compacted
148    Collection<HStoreFile> compactedFilesPreClean = fileManager.getCompactedfiles();
149    assertNotNull(compactedFilesPreClean);
150    assertTrue(compactedFilesPreClean.size() > 0);
151
152    // add the dummy file to the store directory
153    HRegionFileSystem regionFS = region.getRegionFileSystem();
154    Path errFile = regionFS.getStoreFilePath(Bytes.toString(fam), ERROR_FILE);
155    FSDataOutputStream out = regionFS.getFileSystem().create(errFile);
156    out.writeInt(1);
157    out.close();
158
159    StoreFileTrackerForTest storeFileTrackerForTest =
160      new StoreFileTrackerForTest(store.getReadOnlyConfiguration(), true, store.getStoreContext());
161    HStoreFile errStoreFile =
162      new MockHStoreFile(testUtil, errFile, 1, 0, false, 1, storeFileTrackerForTest);
163    fileManager.addCompactionResults(ImmutableList.of(errStoreFile), ImmutableList.of());
164
165    // cleanup compacted files
166    cleaner.chore();
167
168    // make sure the compacted files are cleared
169    Collection<HStoreFile> compactedFilesPostClean = fileManager.getCompactedfiles();
170    assertEquals(1, compactedFilesPostClean.size());
171    for (HStoreFile origFile : compactedFilesPreClean) {
172      assertFalse(compactedFilesPostClean.contains(origFile));
173    }
174
175    // close the region
176    try {
177      region.close();
178    } catch (FailedArchiveException e) {
179      // expected due to errorfile
180      assertEquals(1, e.getFailedFiles().size());
181      assertEquals(ERROR_FILE, e.getFailedFiles().iterator().next().getName());
182    }
183  }
184
185  private HRegion initHRegion(TableDescriptor htd, RegionInfo info) throws IOException {
186    Configuration conf = testUtil.getConfiguration();
187    ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null,
188      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
189    Path tableDir = CommonFSUtils.getTableDir(testDir, htd.getTableName());
190    Path regionDir = new Path(tableDir, info.getEncodedName());
191    Path storeDir = new Path(regionDir, htd.getColumnFamilies()[0].getNameAsString());
192
193    FileSystem errFS = spy(testUtil.getTestFileSystem());
194    // Prior to HBASE-16964, when an exception is thrown archiving any compacted file,
195    // none of the other files are cleared from the compactedfiles list.
196    // Simulate this condition with a dummy file
197    doThrow(new IOException("Error for test")).when(errFS)
198      .rename(eq(new Path(storeDir, ERROR_FILE)), any());
199
200    HRegionFileSystem fs = new HRegionFileSystem(conf, errFS, tableDir, info);
201    final Configuration walConf = new Configuration(conf);
202    CommonFSUtils.setRootDir(walConf, tableDir);
203    final WALFactory wals = new WALFactory(walConf, "log_" + info.getEncodedName());
204    HRegion region = new HRegion(fs, wals.getWAL(info), conf, htd, null);
205
206    region.initialize();
207
208    return region;
209  }
210}