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.master.normalizer;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertTrue;
022import static org.mockito.Matchers.any;
023import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
024import static org.mockito.Mockito.when;
025
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import org.apache.hadoop.hbase.HBaseClassTestRule;
031import org.apache.hadoop.hbase.HBaseIOException;
032import org.apache.hadoop.hbase.RegionMetrics;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.Size;
035import org.apache.hadoop.hbase.TableName;
036import org.apache.hadoop.hbase.client.RegionInfo;
037import org.apache.hadoop.hbase.client.RegionInfoBuilder;
038import org.apache.hadoop.hbase.master.MasterRpcServices;
039import org.apache.hadoop.hbase.master.MasterServices;
040import org.apache.hadoop.hbase.testclassification.MasterTests;
041import org.apache.hadoop.hbase.testclassification.SmallTests;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.junit.BeforeClass;
044import org.junit.ClassRule;
045import org.junit.Rule;
046import org.junit.Test;
047import org.junit.experimental.categories.Category;
048import org.junit.rules.TestName;
049import org.mockito.Mockito;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
054
055import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledResponse;
056
057/**
058 * Tests logic of {@link SimpleRegionNormalizer}.
059 */
060@Category({MasterTests.class, SmallTests.class})
061public class TestSimpleRegionNormalizer {
062
063  @ClassRule
064  public static final HBaseClassTestRule CLASS_RULE =
065      HBaseClassTestRule.forClass(TestSimpleRegionNormalizer.class);
066
067  private static final Logger LOG = LoggerFactory.getLogger(TestSimpleRegionNormalizer.class);
068
069  private static RegionNormalizer normalizer;
070
071  // mocks
072  private static MasterServices masterServices;
073  private static MasterRpcServices masterRpcServices;
074
075  @Rule
076  public TestName name = new TestName();
077
078  @BeforeClass
079  public static void beforeAllTests() throws Exception {
080    normalizer = new SimpleRegionNormalizer();
081  }
082
083  @Test
084  public void testNoNormalizationForMetaTable() throws HBaseIOException {
085    TableName testTable = TableName.META_TABLE_NAME;
086    List<RegionInfo> RegionInfo = new ArrayList<>();
087    Map<byte[], Integer> regionSizes = new HashMap<>();
088
089    setupMocksForNormalizer(regionSizes, RegionInfo);
090    List<NormalizationPlan> plans = normalizer.computePlanForTable(testTable);
091    assertTrue(plans == null);
092  }
093
094  @Test
095  public void testNoNormalizationIfTooFewRegions() throws HBaseIOException {
096    final TableName tableName = TableName.valueOf(name.getMethodName());
097    List<RegionInfo> RegionInfo = new ArrayList<>();
098    Map<byte[], Integer> regionSizes = new HashMap<>();
099    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
100        .setStartKey(Bytes.toBytes("aaa"))
101        .setEndKey(Bytes.toBytes("bbb"))
102        .build();
103    RegionInfo.add(hri1);
104    regionSizes.put(hri1.getRegionName(), 10);
105
106    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
107        .setStartKey(Bytes.toBytes("bbb"))
108        .setEndKey(Bytes.toBytes("ccc"))
109        .build();
110    RegionInfo.add(hri2);
111    regionSizes.put(hri2.getRegionName(), 15);
112
113    setupMocksForNormalizer(regionSizes, RegionInfo);
114    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
115    assertTrue(plans == null);
116  }
117
118  @Test
119  public void testNoNormalizationOnNormalizedCluster() throws HBaseIOException {
120    final TableName tableName = TableName.valueOf(name.getMethodName());
121    List<RegionInfo> RegionInfo = new ArrayList<>();
122    Map<byte[], Integer> regionSizes = new HashMap<>();
123
124    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
125        .setStartKey(Bytes.toBytes("aaa"))
126        .setEndKey(Bytes.toBytes("bbb"))
127        .build();
128    RegionInfo.add(hri1);
129    regionSizes.put(hri1.getRegionName(), 10);
130
131    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
132        .setStartKey(Bytes.toBytes("bbb"))
133        .setEndKey(Bytes.toBytes("ccc"))
134        .build();
135    RegionInfo.add(hri2);
136    regionSizes.put(hri2.getRegionName(), 15);
137
138    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
139        .setStartKey(Bytes.toBytes("ccc"))
140        .setEndKey(Bytes.toBytes("ddd"))
141        .build();
142    RegionInfo.add(hri3);
143    regionSizes.put(hri3.getRegionName(), 8);
144
145    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
146        .setStartKey(Bytes.toBytes("ddd"))
147        .setEndKey(Bytes.toBytes("eee"))
148        .build();
149    regionSizes.put(hri4.getRegionName(), 10);
150
151    setupMocksForNormalizer(regionSizes, RegionInfo);
152    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
153    assertTrue(plans == null);
154  }
155
156  @Test
157  public void testMergeOfSmallRegions() throws HBaseIOException {
158    final TableName tableName = TableName.valueOf(name.getMethodName());
159    List<RegionInfo> RegionInfo = new ArrayList<>();
160    Map<byte[], Integer> regionSizes = new HashMap<>();
161
162    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
163        .setStartKey(Bytes.toBytes("aaa"))
164        .setEndKey(Bytes.toBytes("bbb"))
165        .build();
166    RegionInfo.add(hri1);
167    regionSizes.put(hri1.getRegionName(), 15);
168
169    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
170        .setStartKey(Bytes.toBytes("bbb"))
171        .setEndKey(Bytes.toBytes("ccc"))
172        .build();
173    RegionInfo.add(hri2);
174    regionSizes.put(hri2.getRegionName(), 5);
175
176    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
177        .setStartKey(Bytes.toBytes("ccc"))
178        .setEndKey(Bytes.toBytes("ddd"))
179        .build();
180    RegionInfo.add(hri3);
181    regionSizes.put(hri3.getRegionName(), 5);
182
183    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
184        .setStartKey(Bytes.toBytes("ddd"))
185        .setEndKey(Bytes.toBytes("eee"))
186        .build();
187    RegionInfo.add(hri4);
188    regionSizes.put(hri4.getRegionName(), 15);
189
190    RegionInfo hri5 = RegionInfoBuilder.newBuilder(tableName)
191        .setStartKey(Bytes.toBytes("eee"))
192        .setEndKey(Bytes.toBytes("fff"))
193        .build();
194    RegionInfo.add(hri5);
195    regionSizes.put(hri5.getRegionName(), 16);
196
197    setupMocksForNormalizer(regionSizes, RegionInfo);
198    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
199
200    NormalizationPlan plan = plans.get(0);
201    assertTrue(plan instanceof MergeNormalizationPlan);
202    assertEquals(hri2, ((MergeNormalizationPlan) plan).getFirstRegion());
203    assertEquals(hri3, ((MergeNormalizationPlan) plan).getSecondRegion());
204  }
205
206  // Test for situation illustrated in HBASE-14867
207  @Test
208  public void testMergeOfSecondSmallestRegions() throws HBaseIOException {
209    final TableName tableName = TableName.valueOf(name.getMethodName());
210    List<RegionInfo> RegionInfo = new ArrayList<>();
211    Map<byte[], Integer> regionSizes = new HashMap<>();
212
213    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
214        .setStartKey(Bytes.toBytes("aaa"))
215        .setEndKey(Bytes.toBytes("bbb"))
216        .build();
217    RegionInfo.add(hri1);
218    regionSizes.put(hri1.getRegionName(), 1);
219
220    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
221        .setStartKey(Bytes.toBytes("bbb"))
222        .setEndKey(Bytes.toBytes("ccc"))
223        .build();
224    RegionInfo.add(hri2);
225    regionSizes.put(hri2.getRegionName(), 10000);
226
227    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
228        .setStartKey(Bytes.toBytes("ccc"))
229        .setEndKey(Bytes.toBytes("ddd"))
230        .build();
231    RegionInfo.add(hri3);
232    regionSizes.put(hri3.getRegionName(), 10000);
233
234    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
235        .setStartKey(Bytes.toBytes("ddd"))
236        .setEndKey(Bytes.toBytes("eee"))
237        .build();
238    RegionInfo.add(hri4);
239    regionSizes.put(hri4.getRegionName(), 10000);
240
241    RegionInfo hri5 = RegionInfoBuilder.newBuilder(tableName)
242        .setStartKey(Bytes.toBytes("eee"))
243        .setEndKey(Bytes.toBytes("fff"))
244        .build();
245    RegionInfo.add(hri5);
246    regionSizes.put(hri5.getRegionName(), 2700);
247
248    RegionInfo hri6 = RegionInfoBuilder.newBuilder(tableName)
249        .setStartKey(Bytes.toBytes("fff"))
250        .setEndKey(Bytes.toBytes("ggg"))
251        .build();
252    RegionInfo.add(hri6);
253    regionSizes.put(hri6.getRegionName(), 2700);
254
255    setupMocksForNormalizer(regionSizes, RegionInfo);
256    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
257    NormalizationPlan plan = plans.get(0);
258
259    assertTrue(plan instanceof MergeNormalizationPlan);
260    assertEquals(hri5, ((MergeNormalizationPlan) plan).getFirstRegion());
261    assertEquals(hri6, ((MergeNormalizationPlan) plan).getSecondRegion());
262  }
263
264  @Test
265  public void testMergeOfSmallNonAdjacentRegions() throws HBaseIOException {
266    final TableName tableName = TableName.valueOf(name.getMethodName());
267    List<RegionInfo> RegionInfo = new ArrayList<>();
268    Map<byte[], Integer> regionSizes = new HashMap<>();
269
270    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
271        .setStartKey(Bytes.toBytes("aaa"))
272        .setEndKey(Bytes.toBytes("bbb"))
273        .build();
274    RegionInfo.add(hri1);
275    regionSizes.put(hri1.getRegionName(), 15);
276
277    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
278        .setStartKey(Bytes.toBytes("bbb"))
279        .setEndKey(Bytes.toBytes("ccc"))
280        .build();
281    RegionInfo.add(hri2);
282    regionSizes.put(hri2.getRegionName(), 5);
283
284    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
285        .setStartKey(Bytes.toBytes("ccc"))
286        .setEndKey(Bytes.toBytes("ddd"))
287        .build();
288    RegionInfo.add(hri3);
289    regionSizes.put(hri3.getRegionName(), 16);
290
291    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
292        .setStartKey(Bytes.toBytes("ddd"))
293        .setEndKey(Bytes.toBytes("eee"))
294        .build();
295    RegionInfo.add(hri4);
296    regionSizes.put(hri4.getRegionName(), 15);
297
298    RegionInfo hri5 = RegionInfoBuilder.newBuilder(tableName)
299        .setStartKey(Bytes.toBytes("ddd"))
300        .setEndKey(Bytes.toBytes("eee"))
301        .build();
302    RegionInfo.add(hri4);
303    regionSizes.put(hri5.getRegionName(), 5);
304
305    setupMocksForNormalizer(regionSizes, RegionInfo);
306    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
307
308    assertTrue(plans == null);
309  }
310
311  @Test
312  public void testSplitOfLargeRegion() throws HBaseIOException {
313    final TableName tableName = TableName.valueOf(name.getMethodName());
314    List<RegionInfo> RegionInfo = new ArrayList<>();
315    Map<byte[], Integer> regionSizes = new HashMap<>();
316
317    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
318        .setStartKey(Bytes.toBytes("aaa"))
319        .setEndKey(Bytes.toBytes("bbb"))
320        .build();
321    RegionInfo.add(hri1);
322    regionSizes.put(hri1.getRegionName(), 8);
323
324    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
325        .setStartKey(Bytes.toBytes("bbb"))
326        .setEndKey(Bytes.toBytes("ccc"))
327        .build();
328    RegionInfo.add(hri2);
329    regionSizes.put(hri2.getRegionName(), 6);
330
331    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
332        .setStartKey(Bytes.toBytes("ccc"))
333        .setEndKey(Bytes.toBytes("ddd"))
334        .build();
335    RegionInfo.add(hri3);
336    regionSizes.put(hri3.getRegionName(), 10);
337
338    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
339        .setStartKey(Bytes.toBytes("ddd"))
340        .setEndKey(Bytes.toBytes("eee"))
341        .build();
342    RegionInfo.add(hri4);
343    regionSizes.put(hri4.getRegionName(), 30);
344
345    setupMocksForNormalizer(regionSizes, RegionInfo);
346    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
347    NormalizationPlan plan = plans.get(0);
348
349    assertTrue(plan instanceof SplitNormalizationPlan);
350    assertEquals(hri4, ((SplitNormalizationPlan) plan).getRegionInfo());
351  }
352
353  @SuppressWarnings("MockitoCast")
354  protected void setupMocksForNormalizer(Map<byte[], Integer> regionSizes,
355                                         List<RegionInfo> RegionInfo) {
356    masterServices = Mockito.mock(MasterServices.class, RETURNS_DEEP_STUBS);
357    masterRpcServices = Mockito.mock(MasterRpcServices.class, RETURNS_DEEP_STUBS);
358
359    // for simplicity all regions are assumed to be on one server; doesn't matter to us
360    ServerName sn = ServerName.valueOf("localhost", 0, 1L);
361    when(masterServices.getAssignmentManager().getRegionStates().
362      getRegionsOfTable(any())).thenReturn(RegionInfo);
363    when(masterServices.getAssignmentManager().getRegionStates().
364      getRegionServerOfRegion(any())).thenReturn(sn);
365
366    for (Map.Entry<byte[], Integer> region : regionSizes.entrySet()) {
367      RegionMetrics regionLoad = Mockito.mock(RegionMetrics.class);
368      when(regionLoad.getRegionName()).thenReturn(region.getKey());
369      when(regionLoad.getStoreFileSize())
370        .thenReturn(new Size(region.getValue(), Size.Unit.MEGABYTE));
371
372      // this is possibly broken with jdk9, unclear if false positive or not
373      // suppress it for now, fix it when we get to running tests on 9
374      // see: http://errorprone.info/bugpattern/MockitoCast
375      when((Object) masterServices.getServerManager().getLoad(sn).
376        getRegionMetrics().get(region.getKey())).thenReturn(regionLoad);
377    }
378    try {
379      when(masterRpcServices.isSplitOrMergeEnabled(any(),
380        any())).thenReturn(
381          IsSplitOrMergeEnabledResponse.newBuilder().setEnabled(true).build());
382    } catch (ServiceException se) {
383      LOG.debug("error setting isSplitOrMergeEnabled switch", se);
384    }
385
386    normalizer.setMasterServices(masterServices);
387    normalizer.setMasterRpcServices(masterRpcServices);
388  }
389}