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.client; 019 020import static junit.framework.Assert.assertEquals; 021import static org.junit.Assert.assertNotNull; 022import static org.junit.Assert.assertNull; 023import static org.junit.Assert.assertTrue; 024import static org.junit.Assert.fail; 025 026import java.io.IOException; 027import java.util.ArrayList; 028import java.util.List; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.*; 031import org.apache.hadoop.hbase.HBaseClassTestRule; 032import org.apache.hadoop.hbase.exceptions.ClientExceptionsUtil; 033import org.apache.hadoop.hbase.exceptions.RegionOpeningException; 034import org.apache.hadoop.hbase.quotas.RpcThrottlingException; 035import org.apache.hadoop.hbase.regionserver.HRegionServer; 036import org.apache.hadoop.hbase.regionserver.RSRpcServices; 037import org.apache.hadoop.hbase.testclassification.ClientTests; 038import org.apache.hadoop.hbase.testclassification.MediumTests; 039import org.apache.hadoop.hbase.util.Bytes; 040import org.junit.AfterClass; 041import org.junit.BeforeClass; 042import org.junit.ClassRule; 043import org.junit.Test; 044import org.junit.experimental.categories.Category; 045 046import org.apache.hbase.thirdparty.com.google.protobuf.RpcController; 047import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException; 048 049import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos; 050import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.GetResponse; 051import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 052 053@Category({MediumTests.class, ClientTests.class}) 054public class TestMetaCache { 055 056 @ClassRule 057 public static final HBaseClassTestRule CLASS_RULE = 058 HBaseClassTestRule.forClass(TestMetaCache.class); 059 060 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 061 private static final TableName TABLE_NAME = TableName.valueOf("test_table"); 062 private static final byte[] FAMILY = Bytes.toBytes("fam1"); 063 private static final byte[] QUALIFIER = Bytes.toBytes("qual"); 064 065 private static HRegionServer badRS; 066 067 /** 068 * @throws java.lang.Exception 069 */ 070 @BeforeClass 071 public static void setUpBeforeClass() throws Exception { 072 Configuration conf = TEST_UTIL.getConfiguration(); 073 conf.setStrings(HConstants.REGION_SERVER_IMPL, 074 RegionServerWithFakeRpcServices.class.getName()); 075 TEST_UTIL.startMiniCluster(1); 076 TEST_UTIL.getHBaseCluster().waitForActiveAndReadyMaster(); 077 TEST_UTIL.waitUntilAllRegionsAssigned(TABLE_NAME.META_TABLE_NAME); 078 badRS = TEST_UTIL.getHBaseCluster().getRegionServer(0); 079 assertTrue(badRS.getRSRpcServices() instanceof FakeRSRpcServices); 080 HTableDescriptor table = new HTableDescriptor(TABLE_NAME); 081 HColumnDescriptor fam = new HColumnDescriptor(FAMILY); 082 fam.setMaxVersions(2); 083 table.addFamily(fam); 084 TEST_UTIL.createTable(table, null); 085 } 086 087 088 /** 089 * @throws java.lang.Exception 090 */ 091 @AfterClass 092 public static void tearDownAfterClass() throws Exception { 093 TEST_UTIL.shutdownMiniCluster(); 094 } 095 096 @Test 097 public void testPreserveMetaCacheOnException() throws Exception { 098 ((FakeRSRpcServices)badRS.getRSRpcServices()).setExceptionInjector( 099 new RoundRobinExceptionInjector()); 100 Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); 101 conf.set("hbase.client.retries.number", "1"); 102 ConnectionImplementation conn = 103 (ConnectionImplementation) ConnectionFactory.createConnection(conf); 104 try { 105 Table table = conn.getTable(TABLE_NAME); 106 byte[] row = Bytes.toBytes("row1"); 107 108 Put put = new Put(row); 109 put.addColumn(FAMILY, QUALIFIER, Bytes.toBytes(10)); 110 Get get = new Get(row); 111 Append append = new Append(row); 112 append.addColumn(FAMILY, QUALIFIER, Bytes.toBytes(11)); 113 Increment increment = new Increment(row); 114 increment.addColumn(FAMILY, QUALIFIER, 10); 115 Delete delete = new Delete(row); 116 delete.addColumn(FAMILY, QUALIFIER); 117 RowMutations mutations = new RowMutations(row); 118 mutations.add(put); 119 mutations.add(delete); 120 121 Exception exp; 122 boolean success; 123 for (int i = 0; i < 50; i++) { 124 exp = null; 125 success = false; 126 try { 127 table.put(put); 128 // If at least one operation succeeded, we should have cached the region location. 129 success = true; 130 table.get(get); 131 table.append(append); 132 table.increment(increment); 133 table.delete(delete); 134 table.mutateRow(mutations); 135 } catch (IOException ex) { 136 // Only keep track of the last exception that updated the meta cache 137 if (ClientExceptionsUtil.isMetaClearingException(ex) || success) { 138 exp = ex; 139 } 140 } 141 // Do not test if we did not touch the meta cache in this iteration. 142 if (exp != null && ClientExceptionsUtil.isMetaClearingException(exp)) { 143 assertNull(conn.getCachedLocation(TABLE_NAME, row)); 144 } else if (success) { 145 assertNotNull(conn.getCachedLocation(TABLE_NAME, row)); 146 } 147 } 148 } finally { 149 conn.close(); 150 } 151 } 152 153 @Test 154 public void testCacheClearingOnCallQueueTooBig() throws Exception { 155 ((FakeRSRpcServices)badRS.getRSRpcServices()).setExceptionInjector( 156 new CallQueueTooBigExceptionInjector()); 157 Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); 158 conf.set("hbase.client.retries.number", "2"); 159 conf.set(MetricsConnection.CLIENT_SIDE_METRICS_ENABLED_KEY, "true"); 160 ConnectionImplementation conn = 161 (ConnectionImplementation) ConnectionFactory.createConnection(conf); 162 try { 163 Table table = conn.getTable(TABLE_NAME); 164 byte[] row = Bytes.toBytes("row1"); 165 166 Put put = new Put(row); 167 put.addColumn(FAMILY, QUALIFIER, Bytes.toBytes(10)); 168 table.put(put); 169 170 // obtain the client metrics 171 MetricsConnection metrics = conn.getConnectionMetrics(); 172 long preGetRegionClears = metrics.metaCacheNumClearRegion.getCount(); 173 long preGetServerClears = metrics.metaCacheNumClearServer.getCount(); 174 175 // attempt a get on the test table 176 Get get = new Get(row); 177 try { 178 table.get(get); 179 fail("Expected CallQueueTooBigException"); 180 } catch (RetriesExhaustedException ree) { 181 // expected 182 } 183 184 // verify that no cache clearing took place 185 long postGetRegionClears = metrics.metaCacheNumClearRegion.getCount(); 186 long postGetServerClears = metrics.metaCacheNumClearServer.getCount(); 187 assertEquals(preGetRegionClears, postGetRegionClears); 188 assertEquals(preGetServerClears, postGetServerClears); 189 } finally { 190 conn.close(); 191 } 192 } 193 194 public static List<Throwable> metaCachePreservingExceptions() { 195 return new ArrayList<Throwable>() {{ 196 add(new RegionOpeningException(" ")); 197 add(new RegionTooBusyException("Some old message")); 198 add(new RpcThrottlingException(" ")); 199 add(new MultiActionResultTooLarge(" ")); 200 add(new RetryImmediatelyException(" ")); 201 add(new CallQueueTooBigException()); 202 }}; 203 } 204 205 public static class RegionServerWithFakeRpcServices extends HRegionServer { 206 private FakeRSRpcServices rsRpcServices; 207 208 public RegionServerWithFakeRpcServices(Configuration conf) 209 throws IOException, InterruptedException { 210 super(conf); 211 } 212 213 @Override 214 protected RSRpcServices createRpcServices() throws IOException { 215 this.rsRpcServices = new FakeRSRpcServices(this); 216 return rsRpcServices; 217 } 218 219 public void setExceptionInjector(ExceptionInjector injector) { 220 rsRpcServices.setExceptionInjector(injector); 221 } 222 } 223 224 public static class FakeRSRpcServices extends RSRpcServices { 225 226 private ExceptionInjector exceptions; 227 228 public FakeRSRpcServices(HRegionServer rs) throws IOException { 229 super(rs); 230 exceptions = new RoundRobinExceptionInjector(); 231 } 232 233 public void setExceptionInjector(ExceptionInjector injector) { 234 this.exceptions = injector; 235 } 236 237 @Override 238 public GetResponse get(final RpcController controller, 239 final ClientProtos.GetRequest request) throws ServiceException { 240 exceptions.throwOnGet(this, request); 241 return super.get(controller, request); 242 } 243 244 @Override 245 public ClientProtos.MutateResponse mutate(final RpcController controller, 246 final ClientProtos.MutateRequest request) throws ServiceException { 247 exceptions.throwOnMutate(this, request); 248 return super.mutate(controller, request); 249 } 250 251 @Override 252 public ClientProtos.ScanResponse scan(final RpcController controller, 253 final ClientProtos.ScanRequest request) throws ServiceException { 254 exceptions.throwOnScan(this, request); 255 return super.scan(controller, request); 256 } 257 } 258 259 public static abstract class ExceptionInjector { 260 protected boolean isTestTable(FakeRSRpcServices rpcServices, 261 HBaseProtos.RegionSpecifier regionSpec) throws ServiceException { 262 try { 263 return TABLE_NAME.equals( 264 rpcServices.getRegion(regionSpec).getTableDescriptor().getTableName()); 265 } catch (IOException ioe) { 266 throw new ServiceException(ioe); 267 } 268 } 269 270 public abstract void throwOnGet(FakeRSRpcServices rpcServices, ClientProtos.GetRequest request) 271 throws ServiceException; 272 273 public abstract void throwOnMutate(FakeRSRpcServices rpcServices, ClientProtos.MutateRequest request) 274 throws ServiceException; 275 276 public abstract void throwOnScan(FakeRSRpcServices rpcServices, ClientProtos.ScanRequest request) 277 throws ServiceException; 278 } 279 280 /** 281 * Rotates through the possible cache clearing and non-cache clearing exceptions 282 * for requests. 283 */ 284 public static class RoundRobinExceptionInjector extends ExceptionInjector { 285 private int numReqs = -1; 286 private int expCount = -1; 287 private List<Throwable> metaCachePreservingExceptions = metaCachePreservingExceptions(); 288 289 @Override 290 public void throwOnGet(FakeRSRpcServices rpcServices, ClientProtos.GetRequest request) 291 throws ServiceException { 292 throwSomeExceptions(rpcServices, request.getRegion()); 293 } 294 295 @Override 296 public void throwOnMutate(FakeRSRpcServices rpcServices, ClientProtos.MutateRequest request) 297 throws ServiceException { 298 throwSomeExceptions(rpcServices, request.getRegion()); 299 } 300 301 @Override 302 public void throwOnScan(FakeRSRpcServices rpcServices, ClientProtos.ScanRequest request) 303 throws ServiceException { 304 if (!request.hasScannerId()) { 305 // only handle initial scan requests 306 throwSomeExceptions(rpcServices, request.getRegion()); 307 } 308 } 309 310 /** 311 * Throw some exceptions. Mostly throw exceptions which do not clear meta cache. 312 * Periodically throw NotSevingRegionException which clears the meta cache. 313 * @throws ServiceException 314 */ 315 private void throwSomeExceptions(FakeRSRpcServices rpcServices, 316 HBaseProtos.RegionSpecifier regionSpec) 317 throws ServiceException { 318 if (!isTestTable(rpcServices, regionSpec)) { 319 return; 320 } 321 322 numReqs++; 323 // Succeed every 5 request, throw cache clearing exceptions twice every 5 requests and throw 324 // meta cache preserving exceptions otherwise. 325 if (numReqs % 5 ==0) { 326 return; 327 } else if (numReqs % 5 == 1 || numReqs % 5 == 2) { 328 throw new ServiceException(new NotServingRegionException()); 329 } 330 // Round robin between different special exceptions. 331 // This is not ideal since exception types are not tied to the operation performed here, 332 // But, we don't really care here if we throw MultiActionTooLargeException while doing 333 // single Gets. 334 expCount++; 335 Throwable t = metaCachePreservingExceptions.get( 336 expCount % metaCachePreservingExceptions.size()); 337 throw new ServiceException(t); 338 } 339 } 340 341 /** 342 * Throws CallQueueTooBigException for all gets. 343 */ 344 public static class CallQueueTooBigExceptionInjector extends ExceptionInjector { 345 @Override 346 public void throwOnGet(FakeRSRpcServices rpcServices, ClientProtos.GetRequest request) 347 throws ServiceException { 348 if (isTestTable(rpcServices, request.getRegion())) { 349 throw new ServiceException(new CallQueueTooBigException()); 350 } 351 } 352 353 @Override 354 public void throwOnMutate(FakeRSRpcServices rpcServices, ClientProtos.MutateRequest request) 355 throws ServiceException { 356 } 357 358 @Override 359 public void throwOnScan(FakeRSRpcServices rpcServices, ClientProtos.ScanRequest request) 360 throws ServiceException { 361 } 362 } 363}