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