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}