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.Assert;
044import org.junit.BeforeClass;
045import org.junit.ClassRule;
046import org.junit.Rule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049import org.junit.rules.TestName;
050import org.mockito.Mockito;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
055
056import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledResponse;
057
058/**
059 * Tests logic of {@link SimpleRegionNormalizer}.
060 */
061@Category({MasterTests.class, SmallTests.class})
062public class TestSimpleRegionNormalizer {
063
064  @ClassRule
065  public static final HBaseClassTestRule CLASS_RULE =
066      HBaseClassTestRule.forClass(TestSimpleRegionNormalizer.class);
067
068  private static final Logger LOG = LoggerFactory.getLogger(TestSimpleRegionNormalizer.class);
069
070  private static RegionNormalizer normalizer;
071
072  // mocks
073  private static MasterServices masterServices;
074  private static MasterRpcServices masterRpcServices;
075
076  @Rule
077  public TestName name = new TestName();
078
079  @BeforeClass
080  public static void beforeAllTests() throws Exception {
081    normalizer = new SimpleRegionNormalizer();
082  }
083
084  @Test
085  public void testNoNormalizationForMetaTable() throws HBaseIOException {
086    TableName testTable = TableName.META_TABLE_NAME;
087    List<RegionInfo> RegionInfo = new ArrayList<>();
088    Map<byte[], Integer> regionSizes = new HashMap<>();
089
090    setupMocksForNormalizer(regionSizes, RegionInfo);
091    List<NormalizationPlan> plans = normalizer.computePlanForTable(testTable);
092    assertTrue(plans == null);
093  }
094
095  @Test
096  public void testNoNormalizationIfTooFewRegions() throws HBaseIOException {
097    final TableName tableName = TableName.valueOf(name.getMethodName());
098    List<RegionInfo> RegionInfo = new ArrayList<>();
099    Map<byte[], Integer> regionSizes = new HashMap<>();
100    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
101        .setStartKey(Bytes.toBytes("aaa"))
102        .setEndKey(Bytes.toBytes("bbb"))
103        .build();
104    RegionInfo.add(hri1);
105    regionSizes.put(hri1.getRegionName(), 10);
106
107    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
108        .setStartKey(Bytes.toBytes("bbb"))
109        .setEndKey(Bytes.toBytes("ccc"))
110        .build();
111    RegionInfo.add(hri2);
112    regionSizes.put(hri2.getRegionName(), 15);
113
114    setupMocksForNormalizer(regionSizes, RegionInfo);
115    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
116    assertTrue(plans == null);
117  }
118
119  @Test
120  public void testNoNormalizationOnNormalizedCluster() throws HBaseIOException {
121    final TableName tableName = TableName.valueOf(name.getMethodName());
122    List<RegionInfo> RegionInfo = new ArrayList<>();
123    Map<byte[], Integer> regionSizes = new HashMap<>();
124
125    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
126        .setStartKey(Bytes.toBytes("aaa"))
127        .setEndKey(Bytes.toBytes("bbb"))
128        .build();
129    RegionInfo.add(hri1);
130    regionSizes.put(hri1.getRegionName(), 10);
131
132    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
133        .setStartKey(Bytes.toBytes("bbb"))
134        .setEndKey(Bytes.toBytes("ccc"))
135        .build();
136    RegionInfo.add(hri2);
137    regionSizes.put(hri2.getRegionName(), 15);
138
139    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
140        .setStartKey(Bytes.toBytes("ccc"))
141        .setEndKey(Bytes.toBytes("ddd"))
142        .build();
143    RegionInfo.add(hri3);
144    regionSizes.put(hri3.getRegionName(), 8);
145
146    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
147        .setStartKey(Bytes.toBytes("ddd"))
148        .setEndKey(Bytes.toBytes("eee"))
149        .build();
150    regionSizes.put(hri4.getRegionName(), 10);
151
152    setupMocksForNormalizer(regionSizes, RegionInfo);
153    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
154    assertTrue(plans == null);
155  }
156
157  @Test
158  public void testMergeOfSmallRegions() throws HBaseIOException {
159    final TableName tableName = TableName.valueOf(name.getMethodName());
160    List<RegionInfo> RegionInfo = new ArrayList<>();
161    Map<byte[], Integer> regionSizes = new HashMap<>();
162
163    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
164        .setStartKey(Bytes.toBytes("aaa"))
165        .setEndKey(Bytes.toBytes("bbb"))
166        .build();
167    RegionInfo.add(hri1);
168    regionSizes.put(hri1.getRegionName(), 15);
169
170    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
171        .setStartKey(Bytes.toBytes("bbb"))
172        .setEndKey(Bytes.toBytes("ccc"))
173        .build();
174    RegionInfo.add(hri2);
175    regionSizes.put(hri2.getRegionName(), 5);
176
177    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
178        .setStartKey(Bytes.toBytes("ccc"))
179        .setEndKey(Bytes.toBytes("ddd"))
180        .build();
181    RegionInfo.add(hri3);
182    regionSizes.put(hri3.getRegionName(), 5);
183
184    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
185        .setStartKey(Bytes.toBytes("ddd"))
186        .setEndKey(Bytes.toBytes("eee"))
187        .build();
188    RegionInfo.add(hri4);
189    regionSizes.put(hri4.getRegionName(), 15);
190
191    RegionInfo hri5 = RegionInfoBuilder.newBuilder(tableName)
192        .setStartKey(Bytes.toBytes("eee"))
193        .setEndKey(Bytes.toBytes("fff"))
194        .build();
195    RegionInfo.add(hri5);
196    regionSizes.put(hri5.getRegionName(), 16);
197
198    setupMocksForNormalizer(regionSizes, RegionInfo);
199    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
200
201    NormalizationPlan plan = plans.get(0);
202    assertTrue(plan instanceof MergeNormalizationPlan);
203    assertEquals(hri2, ((MergeNormalizationPlan) plan).getFirstRegion());
204    assertEquals(hri3, ((MergeNormalizationPlan) plan).getSecondRegion());
205  }
206
207  // Test for situation illustrated in HBASE-14867
208  @Test
209  public void testMergeOfSecondSmallestRegions() throws HBaseIOException {
210    final TableName tableName = TableName.valueOf(name.getMethodName());
211    List<RegionInfo> RegionInfo = new ArrayList<>();
212    Map<byte[], Integer> regionSizes = new HashMap<>();
213
214    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
215        .setStartKey(Bytes.toBytes("aaa"))
216        .setEndKey(Bytes.toBytes("bbb"))
217        .build();
218    RegionInfo.add(hri1);
219    regionSizes.put(hri1.getRegionName(), 1);
220
221    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
222        .setStartKey(Bytes.toBytes("bbb"))
223        .setEndKey(Bytes.toBytes("ccc"))
224        .build();
225    RegionInfo.add(hri2);
226    regionSizes.put(hri2.getRegionName(), 10000);
227
228    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
229        .setStartKey(Bytes.toBytes("ccc"))
230        .setEndKey(Bytes.toBytes("ddd"))
231        .build();
232    RegionInfo.add(hri3);
233    regionSizes.put(hri3.getRegionName(), 10000);
234
235    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
236        .setStartKey(Bytes.toBytes("ddd"))
237        .setEndKey(Bytes.toBytes("eee"))
238        .build();
239    RegionInfo.add(hri4);
240    regionSizes.put(hri4.getRegionName(), 10000);
241
242    RegionInfo hri5 = RegionInfoBuilder.newBuilder(tableName)
243        .setStartKey(Bytes.toBytes("eee"))
244        .setEndKey(Bytes.toBytes("fff"))
245        .build();
246    RegionInfo.add(hri5);
247    regionSizes.put(hri5.getRegionName(), 2700);
248
249    RegionInfo hri6 = RegionInfoBuilder.newBuilder(tableName)
250        .setStartKey(Bytes.toBytes("fff"))
251        .setEndKey(Bytes.toBytes("ggg"))
252        .build();
253    RegionInfo.add(hri6);
254    regionSizes.put(hri6.getRegionName(), 2700);
255
256    setupMocksForNormalizer(regionSizes, RegionInfo);
257    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
258    NormalizationPlan plan = plans.get(0);
259
260    assertTrue(plan instanceof MergeNormalizationPlan);
261    assertEquals(hri5, ((MergeNormalizationPlan) plan).getFirstRegion());
262    assertEquals(hri6, ((MergeNormalizationPlan) plan).getSecondRegion());
263  }
264
265  @Test
266  public void testMergeOfSmallNonAdjacentRegions() throws HBaseIOException {
267    final TableName tableName = TableName.valueOf(name.getMethodName());
268    List<RegionInfo> RegionInfo = new ArrayList<>();
269    Map<byte[], Integer> regionSizes = new HashMap<>();
270
271    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
272        .setStartKey(Bytes.toBytes("aaa"))
273        .setEndKey(Bytes.toBytes("bbb"))
274        .build();
275    RegionInfo.add(hri1);
276    regionSizes.put(hri1.getRegionName(), 15);
277
278    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
279        .setStartKey(Bytes.toBytes("bbb"))
280        .setEndKey(Bytes.toBytes("ccc"))
281        .build();
282    RegionInfo.add(hri2);
283    regionSizes.put(hri2.getRegionName(), 5);
284
285    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
286        .setStartKey(Bytes.toBytes("ccc"))
287        .setEndKey(Bytes.toBytes("ddd"))
288        .build();
289    RegionInfo.add(hri3);
290    regionSizes.put(hri3.getRegionName(), 16);
291
292    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
293        .setStartKey(Bytes.toBytes("ddd"))
294        .setEndKey(Bytes.toBytes("eee"))
295        .build();
296    RegionInfo.add(hri4);
297    regionSizes.put(hri4.getRegionName(), 15);
298
299    RegionInfo hri5 = RegionInfoBuilder.newBuilder(tableName)
300        .setStartKey(Bytes.toBytes("ddd"))
301        .setEndKey(Bytes.toBytes("eee"))
302        .build();
303    RegionInfo.add(hri4);
304    regionSizes.put(hri5.getRegionName(), 5);
305
306    setupMocksForNormalizer(regionSizes, RegionInfo);
307    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
308
309    assertTrue(plans == null);
310  }
311
312  @Test
313  public void testSplitOfLargeRegion() throws HBaseIOException {
314    final TableName tableName = TableName.valueOf(name.getMethodName());
315    List<RegionInfo> RegionInfo = new ArrayList<>();
316    Map<byte[], Integer> regionSizes = new HashMap<>();
317
318    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName)
319        .setStartKey(Bytes.toBytes("aaa"))
320        .setEndKey(Bytes.toBytes("bbb"))
321        .build();
322    RegionInfo.add(hri1);
323    regionSizes.put(hri1.getRegionName(), 8);
324
325    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName)
326        .setStartKey(Bytes.toBytes("bbb"))
327        .setEndKey(Bytes.toBytes("ccc"))
328        .build();
329    RegionInfo.add(hri2);
330    regionSizes.put(hri2.getRegionName(), 6);
331
332    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName)
333        .setStartKey(Bytes.toBytes("ccc"))
334        .setEndKey(Bytes.toBytes("ddd"))
335        .build();
336    RegionInfo.add(hri3);
337    regionSizes.put(hri3.getRegionName(), 10);
338
339    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName)
340        .setStartKey(Bytes.toBytes("ddd"))
341        .setEndKey(Bytes.toBytes("eee"))
342        .build();
343    RegionInfo.add(hri4);
344    regionSizes.put(hri4.getRegionName(), 30);
345
346    setupMocksForNormalizer(regionSizes, RegionInfo);
347    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
348    NormalizationPlan plan = plans.get(0);
349
350    assertTrue(plan instanceof SplitNormalizationPlan);
351    assertEquals(hri4, ((SplitNormalizationPlan) plan).getRegionInfo());
352  }
353
354  @Test
355  public void testSplitWithTargetRegionCount() throws Exception {
356    final TableName tableName = TableName.valueOf(name.getMethodName());
357    List<RegionInfo> RegionInfo = new ArrayList<>();
358    Map<byte[], Integer> regionSizes = new HashMap<>();
359
360    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("aaa"))
361        .setEndKey(Bytes.toBytes("bbb")).build();
362    RegionInfo.add(hri1);
363    regionSizes.put(hri1.getRegionName(), 20);
364
365    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("bbb"))
366        .setEndKey(Bytes.toBytes("ccc")).build();
367    RegionInfo.add(hri2);
368    regionSizes.put(hri2.getRegionName(), 40);
369
370    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("ccc"))
371        .setEndKey(Bytes.toBytes("ddd")).build();
372    RegionInfo.add(hri3);
373    regionSizes.put(hri3.getRegionName(), 60);
374
375    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("ddd"))
376        .setEndKey(Bytes.toBytes("eee")).build();
377    RegionInfo.add(hri4);
378    regionSizes.put(hri4.getRegionName(), 80);
379
380    RegionInfo hri5 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("eee"))
381        .setEndKey(Bytes.toBytes("fff")).build();
382    RegionInfo.add(hri5);
383    regionSizes.put(hri5.getRegionName(), 100);
384
385    RegionInfo hri6 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("fff"))
386        .setEndKey(Bytes.toBytes("ggg")).build();
387    RegionInfo.add(hri6);
388    regionSizes.put(hri6.getRegionName(), 120);
389
390    setupMocksForNormalizer(regionSizes, RegionInfo);
391
392    // test when target region size is 20
393    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionSize())
394        .thenReturn(20L);
395    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
396    Assert.assertEquals(4, plans.size());
397
398    for (NormalizationPlan plan : plans) {
399      assertTrue(plan instanceof SplitNormalizationPlan);
400    }
401
402    // test when target region size is 200
403    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionSize())
404        .thenReturn(200L);
405    plans = normalizer.computePlanForTable(tableName);
406    Assert.assertEquals(2, plans.size());
407    NormalizationPlan plan = plans.get(0);
408    assertTrue(plan instanceof MergeNormalizationPlan);
409    assertEquals(hri1, ((MergeNormalizationPlan) plan).getFirstRegion());
410    assertEquals(hri2, ((MergeNormalizationPlan) plan).getSecondRegion());
411  }
412
413  @Test
414  public void testSplitWithTargetRegionSize() throws Exception {
415    final TableName tableName = TableName.valueOf(name.getMethodName());
416    List<RegionInfo> RegionInfo = new ArrayList<>();
417    Map<byte[], Integer> regionSizes = new HashMap<>();
418
419    RegionInfo hri1 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("aaa"))
420        .setEndKey(Bytes.toBytes("bbb")).build();
421    RegionInfo.add(hri1);
422    regionSizes.put(hri1.getRegionName(), 20);
423
424    RegionInfo hri2 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("bbb"))
425        .setEndKey(Bytes.toBytes("ccc")).build();
426    RegionInfo.add(hri2);
427    regionSizes.put(hri2.getRegionName(), 40);
428
429    RegionInfo hri3 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("ccc"))
430        .setEndKey(Bytes.toBytes("ddd")).build();
431    RegionInfo.add(hri3);
432    regionSizes.put(hri3.getRegionName(), 60);
433
434    RegionInfo hri4 = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("ddd"))
435        .setEndKey(Bytes.toBytes("eee")).build();
436    RegionInfo.add(hri4);
437    regionSizes.put(hri4.getRegionName(), 80);
438
439    setupMocksForNormalizer(regionSizes, RegionInfo);
440
441    // test when target region count is 8
442    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionCount())
443        .thenReturn(8);
444    List<NormalizationPlan> plans = normalizer.computePlanForTable(tableName);
445    Assert.assertEquals(2, plans.size());
446
447    for (NormalizationPlan plan : plans) {
448      assertTrue(plan instanceof SplitNormalizationPlan);
449    }
450
451    // test when target region count is 3
452    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionCount())
453        .thenReturn(3);
454    plans = normalizer.computePlanForTable(tableName);
455    Assert.assertEquals(1, plans.size());
456    NormalizationPlan plan = plans.get(0);
457    assertTrue(plan instanceof MergeNormalizationPlan);
458    assertEquals(hri1, ((MergeNormalizationPlan) plan).getFirstRegion());
459    assertEquals(hri2, ((MergeNormalizationPlan) plan).getSecondRegion());
460  }
461
462  @SuppressWarnings("MockitoCast")
463  protected void setupMocksForNormalizer(Map<byte[], Integer> regionSizes,
464                                         List<RegionInfo> RegionInfo) {
465    masterServices = Mockito.mock(MasterServices.class, RETURNS_DEEP_STUBS);
466    masterRpcServices = Mockito.mock(MasterRpcServices.class, RETURNS_DEEP_STUBS);
467
468    // for simplicity all regions are assumed to be on one server; doesn't matter to us
469    ServerName sn = ServerName.valueOf("localhost", 0, 1L);
470    when(masterServices.getAssignmentManager().getRegionStates().
471      getRegionsOfTable(any())).thenReturn(RegionInfo);
472    when(masterServices.getAssignmentManager().getRegionStates().
473      getRegionServerOfRegion(any())).thenReturn(sn);
474
475    for (Map.Entry<byte[], Integer> region : regionSizes.entrySet()) {
476      RegionMetrics regionLoad = Mockito.mock(RegionMetrics.class);
477      when(regionLoad.getRegionName()).thenReturn(region.getKey());
478      when(regionLoad.getStoreFileSize())
479        .thenReturn(new Size(region.getValue(), Size.Unit.MEGABYTE));
480
481      // this is possibly broken with jdk9, unclear if false positive or not
482      // suppress it for now, fix it when we get to running tests on 9
483      // see: http://errorprone.info/bugpattern/MockitoCast
484      when((Object) masterServices.getServerManager().getLoad(sn).
485        getRegionMetrics().get(region.getKey())).thenReturn(regionLoad);
486    }
487    try {
488      when(masterRpcServices.isSplitOrMergeEnabled(any(),
489        any())).thenReturn(
490          IsSplitOrMergeEnabledResponse.newBuilder().setEnabled(true).build());
491    } catch (ServiceException se) {
492      LOG.debug("error setting isSplitOrMergeEnabled switch", se);
493    }
494
495    normalizer.setMasterServices(masterServices);
496    normalizer.setMasterRpcServices(masterRpcServices);
497  }
498}