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.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.mockito.Mockito.atLeastOnce;
024import static org.mockito.Mockito.mock;
025import static org.mockito.Mockito.reset;
026import static org.mockito.Mockito.verify;
027
028import java.io.IOException;
029import java.util.Arrays;
030import java.util.List;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseTestingUtility;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.TableName;
036import org.apache.hadoop.hbase.ipc.HBaseRpcController;
037import org.apache.hadoop.hbase.testclassification.MediumTests;
038import org.apache.hadoop.hbase.util.Bytes;
039import org.apache.log4j.Appender;
040import org.apache.log4j.Level;
041import org.apache.log4j.LogManager;
042import org.apache.log4j.spi.LoggingEvent;
043import org.junit.After;
044import org.junit.Before;
045import org.junit.ClassRule;
046import org.junit.Test;
047import org.junit.experimental.categories.Category;
048import org.junit.runner.RunWith;
049import org.junit.runners.Parameterized;
050import org.mockito.ArgumentCaptor;
051import org.mockito.Mockito;
052
053import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
054import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
055
056import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
057import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.Action;
058import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MultiRequest;
059import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.RegionAction;
060import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
061
062/**
063 * Tests logging of large batch commands via Multi. Tests are fast, but uses a mini-cluster (to test
064 * via "Multi" commands) so classified as MediumTests
065 */
066@RunWith(Parameterized.class)
067@Category(MediumTests.class)
068public class TestMultiLogThreshold {
069
070  @ClassRule
071  public static final HBaseClassTestRule CLASS_RULE =
072    HBaseClassTestRule.forClass(TestMultiLogThreshold.class);
073
074  private static final TableName NAME = TableName.valueOf("tableName");
075  private static final byte[] TEST_FAM = Bytes.toBytes("fam");
076
077  private HBaseTestingUtility util;
078  private Configuration conf;
079  private int threshold;
080  private HRegionServer rs;
081  private RSRpcServices services;
082
083  private Appender appender;
084
085  @Parameterized.Parameter
086  public static boolean rejectLargeBatchOp;
087
088  @Parameterized.Parameters
089  public static List<Object[]> params() {
090    return Arrays.asList(new Object[] { false }, new Object[] { true });
091  }
092
093  @Before
094  public void setupTest() throws Exception {
095    util = new HBaseTestingUtility();
096    conf = util.getConfiguration();
097    threshold =
098      conf.getInt(HConstants.BATCH_ROWS_THRESHOLD_NAME, HConstants.BATCH_ROWS_THRESHOLD_DEFAULT);
099    conf.setBoolean("hbase.rpc.rows.size.threshold.reject", rejectLargeBatchOp);
100    util.startMiniCluster();
101    util.createTable(NAME, TEST_FAM);
102    rs = util.getRSForFirstRegionInTable(NAME);
103    appender = mock(Appender.class);
104    LogManager.getLogger(RSRpcServices.class).addAppender(appender);
105  }
106
107  @After
108  public void tearDown() throws Exception {
109    LogManager.getLogger(RSRpcServices.class).removeAppender(appender);
110    util.shutdownMiniCluster();
111  }
112
113  private enum ActionType {
114    REGION_ACTIONS, ACTIONS;
115  }
116
117  /**
118   * Sends a multi request with a certain amount of rows, will populate Multi command with either
119   * "rows" number of RegionActions with one Action each or one RegionAction with "rows" number of
120   * Actions
121   */
122  private void sendMultiRequest(int rows, ActionType actionType)
123    throws ServiceException, IOException {
124    RpcController rpcc = Mockito.mock(HBaseRpcController.class);
125    MultiRequest.Builder builder = MultiRequest.newBuilder();
126    int numRAs = 1;
127    int numAs = 1;
128    switch (actionType) {
129      case REGION_ACTIONS:
130        numRAs = rows;
131        break;
132      case ACTIONS:
133        numAs = rows;
134        break;
135    }
136    for (int i = 0; i < numRAs; i++) {
137      RegionAction.Builder rab = RegionAction.newBuilder();
138      rab.setRegion(RequestConverter.buildRegionSpecifier(
139        HBaseProtos.RegionSpecifier.RegionSpecifierType.REGION_NAME,
140        new String("someStuff" + i).getBytes()));
141      for (int j = 0; j < numAs; j++) {
142        Action.Builder ab = Action.newBuilder();
143        rab.addAction(ab.build());
144      }
145      builder.addRegionAction(rab.build());
146    }
147    services = new RSRpcServices(rs);
148    services.multi(rpcc, builder.build());
149  }
150
151  private void assertLogBatchWarnings(boolean expected) {
152    ArgumentCaptor<LoggingEvent> captor = ArgumentCaptor.forClass(LoggingEvent.class);
153    verify(appender, atLeastOnce()).doAppend(captor.capture());
154    boolean actual = false;
155    for (LoggingEvent event : captor.getAllValues()) {
156      if (event.getLevel() == Level.WARN &&
157        event.getRenderedMessage().contains("Large batch operation detected")) {
158        actual = true;
159        break;
160      }
161    }
162    reset(appender);
163    assertEquals(expected, actual);
164  }
165
166  @Test
167  public void testMultiLogThresholdRegionActions() throws ServiceException, IOException {
168    try {
169      sendMultiRequest(threshold + 1, ActionType.REGION_ACTIONS);
170      assertFalse(rejectLargeBatchOp);
171    } catch (ServiceException e) {
172      assertTrue(rejectLargeBatchOp);
173    }
174    assertLogBatchWarnings(true);
175
176    sendMultiRequest(threshold, ActionType.REGION_ACTIONS);
177    assertLogBatchWarnings(false);
178
179    try {
180      sendMultiRequest(threshold + 1, ActionType.ACTIONS);
181      assertFalse(rejectLargeBatchOp);
182    } catch (ServiceException e) {
183      assertTrue(rejectLargeBatchOp);
184    }
185    assertLogBatchWarnings(true);
186
187    sendMultiRequest(threshold, ActionType.ACTIONS);
188    assertLogBatchWarnings(false);
189  }
190}