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.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory.TRACKER_IMPL;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024import static org.mockito.Mockito.mock;
025import static org.mockito.Mockito.when;
026
027import java.io.IOException;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.List;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.FileSystem;
033import org.apache.hadoop.fs.Path;
034import org.apache.hadoop.hbase.Cell;
035import org.apache.hadoop.hbase.CellUtil;
036import org.apache.hadoop.hbase.HBaseTestingUtil;
037import org.apache.hadoop.hbase.HConstants;
038import org.apache.hadoop.hbase.Stoppable;
039import org.apache.hadoop.hbase.TableName;
040import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
041import org.apache.hadoop.hbase.client.Durability;
042import org.apache.hadoop.hbase.client.Get;
043import org.apache.hadoop.hbase.client.Put;
044import org.apache.hadoop.hbase.client.RegionInfo;
045import org.apache.hadoop.hbase.client.RegionInfoBuilder;
046import org.apache.hadoop.hbase.client.Result;
047import org.apache.hadoop.hbase.client.TableDescriptor;
048import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
049import org.apache.hadoop.hbase.regionserver.storefiletracker.FailingStoreFileTrackerForTest;
050import org.apache.hadoop.hbase.testclassification.MediumTests;
051import org.apache.hadoop.hbase.testclassification.RegionServerTests;
052import org.apache.hadoop.hbase.util.Bytes;
053import org.apache.hadoop.hbase.util.CommonFSUtils;
054import org.apache.hadoop.hbase.util.StoppableImplementation;
055import org.apache.hadoop.hbase.wal.WALFactory;
056import org.junit.jupiter.api.BeforeEach;
057import org.junit.jupiter.api.Tag;
058import org.junit.jupiter.api.Test;
059import org.junit.jupiter.api.TestInfo;
060
061@Tag(RegionServerTests.TAG)
062@Tag(MediumTests.TAG)
063public class TestStoreFileRefresherChore {
064
065  private HBaseTestingUtil TEST_UTIL;
066  private Path testDir;
067
068  private String methodName;
069
070  @BeforeEach
071  public void setUp(TestInfo testInfo) throws IOException {
072    this.methodName = testInfo.getTestMethod().get().getName();
073    TEST_UTIL = new HBaseTestingUtil();
074    testDir = TEST_UTIL.getDataTestDir("TestStoreFileRefresherChore");
075    CommonFSUtils.setRootDir(TEST_UTIL.getConfiguration(), testDir);
076  }
077
078  private TableDescriptor getTableDesc(TableName tableName, int regionReplication,
079    String trackerName, byte[]... families) {
080    return getTableDesc(tableName, regionReplication, false, trackerName, families);
081  }
082
083  private TableDescriptor getTableDesc(TableName tableName, int regionReplication, boolean readOnly,
084    String trackerName, byte[]... families) {
085    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName)
086      .setRegionReplication(regionReplication).setReadOnly(readOnly);
087    if (trackerName != null) {
088      builder.setValue(TRACKER_IMPL, trackerName);
089    }
090    Arrays.stream(families).map(family -> ColumnFamilyDescriptorBuilder.newBuilder(family)
091      .setMaxVersions(Integer.MAX_VALUE).build()).forEachOrdered(builder::setColumnFamily);
092    return builder.build();
093  }
094
095  public static class FailingHRegionFileSystem extends HRegionFileSystem {
096    public boolean fail = false;
097
098    FailingHRegionFileSystem(Configuration conf, FileSystem fs, Path tableDir,
099      RegionInfo regionInfo) {
100      super(conf, fs, tableDir, regionInfo);
101    }
102
103  }
104
105  private HRegion initHRegion(TableDescriptor htd, byte[] startKey, byte[] stopKey, int replicaId)
106    throws IOException {
107    Configuration conf = TEST_UTIL.getConfiguration();
108    Path tableDir = CommonFSUtils.getTableDir(testDir, htd.getTableName());
109    RegionInfo info = RegionInfoBuilder.newBuilder(htd.getTableName()).setStartKey(startKey)
110      .setEndKey(stopKey).setRegionId(0L).setReplicaId(replicaId).build();
111    HRegionFileSystem fs =
112      new FailingHRegionFileSystem(conf, tableDir.getFileSystem(conf), tableDir, info);
113    final Configuration walConf = new Configuration(conf);
114    CommonFSUtils.setRootDir(walConf, tableDir);
115    final WALFactory wals = new WALFactory(walConf, "log_" + replicaId);
116    ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null,
117      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
118    HRegion region = new HRegion(fs, wals.getWAL(info), conf, htd, null);
119    fs.createRegionOnFileSystem(walConf, fs.getFileSystem(), tableDir, info);
120    region.initialize();
121    return region;
122  }
123
124  private void putData(Region region, int startRow, int numRows, byte[] qf, byte[]... families)
125    throws IOException {
126    for (int i = startRow; i < startRow + numRows; i++) {
127      Put put = new Put(Bytes.toBytes("" + i));
128      put.setDurability(Durability.SKIP_WAL);
129      for (byte[] family : families) {
130        put.addColumn(family, qf, null);
131      }
132      region.put(put);
133    }
134  }
135
136  private void verifyDataExpectFail(Region newReg, int startRow, int numRows, byte[] qf,
137    byte[]... families) throws IOException {
138    boolean threw = false;
139    try {
140      verifyData(newReg, startRow, numRows, qf, families);
141    } catch (AssertionError e) {
142      threw = true;
143    }
144    if (!threw) {
145      fail("Expected data verification to fail");
146    }
147  }
148
149  private void verifyData(Region newReg, int startRow, int numRows, byte[] qf, byte[]... families)
150    throws IOException {
151    for (int i = startRow; i < startRow + numRows; i++) {
152      byte[] row = Bytes.toBytes("" + i);
153      Get get = new Get(row);
154      for (byte[] family : families) {
155        get.addColumn(family, qf);
156      }
157      Result result = newReg.get(get);
158      Cell[] raw = result.rawCells();
159      assertEquals(families.length, result.size());
160      for (int j = 0; j < families.length; j++) {
161        assertTrue(CellUtil.matchingRows(raw[j], row));
162        assertTrue(CellUtil.matchingFamily(raw[j], families[j]));
163        assertTrue(CellUtil.matchingQualifier(raw[j], qf));
164      }
165    }
166  }
167
168  static class StaleStorefileRefresherChore extends StorefileRefresherChore {
169    boolean isStale = false;
170
171    public StaleStorefileRefresherChore(int period, HRegionServer regionServer,
172      Stoppable stoppable) {
173      super(period, false, regionServer, stoppable);
174    }
175
176    @Override
177    protected boolean isRegionStale(String encodedName, long time) {
178      return isStale;
179    }
180  }
181
182  @Test
183  public void testIsStale() throws IOException {
184    int period = 0;
185    byte[][] families = new byte[][] { Bytes.toBytes("cf") };
186    byte[] qf = Bytes.toBytes("cq");
187
188    HRegionServer regionServer = mock(HRegionServer.class);
189    List<HRegion> regions = new ArrayList<>();
190    when(regionServer.getOnlineRegionsLocalContext()).thenReturn(regions);
191    when(regionServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
192
193    String trackerName = FailingStoreFileTrackerForTest.class.getName();
194    TableDescriptor htd = getTableDesc(TableName.valueOf(methodName), 2, trackerName, families);
195    HRegion primary = initHRegion(htd, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, 0);
196    HRegion replica1 = initHRegion(htd, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, 1);
197    regions.add(primary);
198    regions.add(replica1);
199
200    StaleStorefileRefresherChore chore =
201      new StaleStorefileRefresherChore(period, regionServer, new StoppableImplementation());
202
203    // write some data to primary and flush
204    putData(primary, 0, 100, qf, families);
205    primary.flush(true);
206    verifyData(primary, 0, 100, qf, families);
207
208    verifyDataExpectFail(replica1, 0, 100, qf, families);
209    chore.chore();
210    verifyData(replica1, 0, 100, qf, families);
211
212    // simulate an fs failure where we cannot refresh the store files for the replica
213    ((FailingHRegionFileSystem) replica1.getRegionFileSystem()).fail = true;
214
215    // write some more data to primary and flush
216    putData(primary, 100, 100, qf, families);
217    primary.flush(true);
218    verifyData(primary, 0, 200, qf, families);
219
220    chore.chore(); // should not throw ex, but we cannot refresh the store files
221
222    verifyData(replica1, 0, 100, qf, families);
223    verifyDataExpectFail(replica1, 100, 100, qf, families);
224
225    chore.isStale = true;
226    chore.chore(); // now after this, we cannot read back any value
227    try {
228      verifyData(replica1, 0, 100, qf, families);
229      fail("should have failed with IOException");
230    } catch (IOException ex) {
231      // expected
232    }
233  }
234
235  @Test
236  public void testRefreshReadOnlyTable() throws IOException {
237    int period = 0;
238    byte[][] families = new byte[][] { Bytes.toBytes("cf") };
239    byte[] qf = Bytes.toBytes("cq");
240
241    HRegionServer regionServer = mock(HRegionServer.class);
242    List<HRegion> regions = new ArrayList<>();
243    when(regionServer.getOnlineRegionsLocalContext()).thenReturn(regions);
244    when(regionServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
245
246    TableDescriptor htd = getTableDesc(TableName.valueOf(methodName), 2, null, families);
247    HRegion primary = initHRegion(htd, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, 0);
248    HRegion replica1 = initHRegion(htd, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, 1);
249    regions.add(primary);
250    regions.add(replica1);
251
252    StorefileRefresherChore chore =
253      new StorefileRefresherChore(period, false, regionServer, new StoppableImplementation());
254
255    // write some data to primary and flush
256    putData(primary, 0, 100, qf, families);
257    primary.flush(true);
258    verifyData(primary, 0, 100, qf, families);
259
260    verifyDataExpectFail(replica1, 0, 100, qf, families);
261    chore.chore();
262    verifyData(replica1, 0, 100, qf, families);
263
264    // write some data to primary and flush before refresh the store files for the replica
265    putData(primary, 100, 100, qf, families);
266    primary.flush(true);
267    verifyData(primary, 0, 200, qf, families);
268
269    // then the table is set to readonly
270    htd = getTableDesc(TableName.valueOf(methodName), 2, true, null, families);
271    primary.setTableDescriptor(htd);
272    replica1.setTableDescriptor(htd);
273
274    chore.chore(); // we cannot refresh the store files
275    verifyDataExpectFail(replica1, 100, 100, qf, families);
276  }
277
278}