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