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 java.lang.String.format;
021import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.DEFAULT_MERGE_MIN_REGION_AGE_DAYS;
022import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_ENABLED_KEY;
023import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_MIN_REGION_AGE_DAYS_KEY;
024import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_MIN_REGION_SIZE_MB_KEY;
025import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MIN_REGION_COUNT_KEY;
026import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.SPLIT_ENABLED_KEY;
027import static org.hamcrest.MatcherAssert.assertThat;
028import static org.hamcrest.Matchers.contains;
029import static org.hamcrest.Matchers.empty;
030import static org.hamcrest.Matchers.everyItem;
031import static org.hamcrest.Matchers.greaterThanOrEqualTo;
032import static org.hamcrest.Matchers.instanceOf;
033import static org.hamcrest.Matchers.not;
034import static org.junit.Assert.assertEquals;
035import static org.junit.Assert.assertFalse;
036import static org.junit.Assert.assertTrue;
037import static org.mockito.ArgumentMatchers.any;
038import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
039import static org.mockito.Mockito.when;
040import java.time.Instant;
041import java.time.Period;
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.HashMap;
045import java.util.List;
046import java.util.Map;
047import org.apache.hadoop.conf.Configuration;
048import org.apache.hadoop.hbase.HBaseClassTestRule;
049import org.apache.hadoop.hbase.HBaseConfiguration;
050import org.apache.hadoop.hbase.RegionMetrics;
051import org.apache.hadoop.hbase.ServerName;
052import org.apache.hadoop.hbase.Size;
053import org.apache.hadoop.hbase.TableName;
054import org.apache.hadoop.hbase.TableNameTestRule;
055import org.apache.hadoop.hbase.client.RegionInfo;
056import org.apache.hadoop.hbase.client.RegionInfoBuilder;
057import org.apache.hadoop.hbase.master.MasterServices;
058import org.apache.hadoop.hbase.master.RegionState;
059import org.apache.hadoop.hbase.testclassification.MasterTests;
060import org.apache.hadoop.hbase.testclassification.SmallTests;
061import org.apache.hadoop.hbase.util.Bytes;
062import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
063import org.junit.Before;
064import org.junit.ClassRule;
065import org.junit.Rule;
066import org.junit.Test;
067import org.junit.experimental.categories.Category;
068import org.mockito.Mockito;
069
070/**
071 * Tests logic of {@link SimpleRegionNormalizer}.
072 */
073@Category({MasterTests.class, SmallTests.class})
074public class TestSimpleRegionNormalizer {
075
076  @ClassRule
077  public static final HBaseClassTestRule CLASS_RULE =
078      HBaseClassTestRule.forClass(TestSimpleRegionNormalizer.class);
079
080  private Configuration conf;
081  private SimpleRegionNormalizer normalizer;
082  private MasterServices masterServices;
083
084  @Rule
085  public TableNameTestRule name = new TableNameTestRule();
086
087  @Before
088  public void before() {
089    conf = HBaseConfiguration.create();
090  }
091
092  @Test
093  public void testNoNormalizationForMetaTable() {
094    TableName testTable = TableName.META_TABLE_NAME;
095    List<RegionInfo> RegionInfo = new ArrayList<>();
096    Map<byte[], Integer> regionSizes = new HashMap<>();
097
098    setupMocksForNormalizer(regionSizes, RegionInfo);
099    List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
100    assertThat(plans, empty());
101  }
102
103  @Test
104  public void testNoNormalizationIfTooFewRegions() {
105    final TableName tableName = name.getTableName();
106    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 2);
107    final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 10, 15);
108    setupMocksForNormalizer(regionSizes, regionInfos);
109
110    List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
111    assertThat(plans, empty());
112  }
113
114  @Test
115  public void testNoNormalizationOnNormalizedCluster() {
116    final TableName tableName = name.getTableName();
117    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4);
118    final Map<byte[], Integer> regionSizes =
119      createRegionSizesMap(regionInfos, 10, 15, 8, 10);
120    setupMocksForNormalizer(regionSizes, regionInfos);
121
122    List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
123    assertThat(plans, empty());
124  }
125
126  private void noNormalizationOnTransitioningRegions(final RegionState.State state) {
127    final TableName tableName = name.getTableName();
128    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 3);
129    final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 10, 1, 100);
130
131    setupMocksForNormalizer(regionSizes, regionInfos);
132    when(masterServices.getAssignmentManager().getRegionStates()
133      .getRegionState(any(RegionInfo.class)))
134      .thenReturn(RegionState.createForTesting(null, state));
135    assertThat(normalizer.getMinRegionCount(), greaterThanOrEqualTo(regionInfos.size()));
136
137    List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
138    assertThat(format("Unexpected plans for RegionState %s", state), plans, empty());
139  }
140
141  @Test
142  public void testNoNormalizationOnMergingNewRegions() {
143    noNormalizationOnTransitioningRegions(RegionState.State.MERGING_NEW);
144  }
145
146  @Test
147  public void testNoNormalizationOnMergingRegions() {
148    noNormalizationOnTransitioningRegions(RegionState.State.MERGING);
149  }
150
151  @Test
152  public void testNoNormalizationOnMergedRegions() {
153    noNormalizationOnTransitioningRegions(RegionState.State.MERGED);
154  }
155
156  @Test
157  public void testNoNormalizationOnSplittingNewRegions() {
158    noNormalizationOnTransitioningRegions(RegionState.State.SPLITTING_NEW);
159  }
160
161  @Test
162  public void testNoNormalizationOnSplittingRegions() {
163    noNormalizationOnTransitioningRegions(RegionState.State.SPLITTING);
164  }
165
166  @Test
167  public void testNoNormalizationOnSplitRegions() {
168    noNormalizationOnTransitioningRegions(RegionState.State.SPLIT);
169  }
170
171  @Test
172  public void testMergeOfSmallRegions() {
173    final TableName tableName = name.getTableName();
174    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5);
175    final Map<byte[], Integer> regionSizes =
176      createRegionSizesMap(regionInfos, 15, 5, 5, 15, 16);
177    setupMocksForNormalizer(regionSizes, regionInfos);
178
179    assertThat(
180      normalizer.computePlansForTable(tableName),
181      contains(new MergeNormalizationPlan.Builder()
182        .addTarget(regionInfos.get(1), 5)
183        .addTarget(regionInfos.get(2), 5)
184        .build()));
185  }
186
187  // Test for situation illustrated in HBASE-14867
188  @Test
189  public void testMergeOfSecondSmallestRegions() {
190    final TableName tableName = name.getTableName();
191    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 6);
192    final Map<byte[], Integer> regionSizes =
193      createRegionSizesMap(regionInfos, 1, 10000, 10000, 10000, 2700, 2700);
194    setupMocksForNormalizer(regionSizes, regionInfos);
195
196    assertThat(
197      normalizer.computePlansForTable(tableName),
198      contains(new MergeNormalizationPlan.Builder()
199        .addTarget(regionInfos.get(4), 2700)
200        .addTarget(regionInfos.get(5), 2700)
201        .build()));
202  }
203
204  @Test
205  public void testMergeOfSmallNonAdjacentRegions() {
206    final TableName tableName = name.getTableName();
207    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5);
208    final Map<byte[], Integer> regionSizes =
209      createRegionSizesMap(regionInfos, 15, 5, 16, 15, 5);
210    setupMocksForNormalizer(regionSizes, regionInfos);
211
212    List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
213    assertThat(plans, empty());
214  }
215
216  @Test
217  public void testSplitOfLargeRegion() {
218    final TableName tableName = name.getTableName();
219    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4);
220    final Map<byte[], Integer> regionSizes =
221      createRegionSizesMap(regionInfos, 8, 6, 10, 30);
222    setupMocksForNormalizer(regionSizes, regionInfos);
223
224    assertThat(normalizer.computePlansForTable(tableName), contains(
225      new SplitNormalizationPlan(regionInfos.get(3), 30)));
226  }
227
228  @Test
229  public void testWithTargetRegionSize() throws Exception {
230    final TableName tableName = name.getTableName();
231    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 6);
232    final Map<byte[], Integer> regionSizes =
233      createRegionSizesMap(regionInfos, 20, 40, 60, 80, 100, 120);
234    setupMocksForNormalizer(regionSizes, regionInfos);
235
236    // test when target region size is 20
237    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionSize())
238        .thenReturn(20L);
239    assertThat(normalizer.computePlansForTable(tableName), contains(
240      new SplitNormalizationPlan(regionInfos.get(2), 60),
241      new SplitNormalizationPlan(regionInfos.get(3), 80),
242      new SplitNormalizationPlan(regionInfos.get(4), 100),
243      new SplitNormalizationPlan(regionInfos.get(5), 120)
244    ));
245
246    // test when target region size is 200
247    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionSize())
248        .thenReturn(200L);
249    assertThat(
250      normalizer.computePlansForTable(tableName),
251      contains(
252        new MergeNormalizationPlan.Builder()
253          .addTarget(regionInfos.get(0), 20)
254          .addTarget(regionInfos.get(1), 40)
255          .addTarget(regionInfos.get(2), 60)
256          .addTarget(regionInfos.get(3), 80)
257          .build()));
258  }
259
260  @Test
261  public void testSplitWithTargetRegionCount() throws Exception {
262    final TableName tableName = name.getTableName();
263    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4);
264    final Map<byte[], Integer> regionSizes =
265      createRegionSizesMap(regionInfos, 20, 40, 60, 80);
266    setupMocksForNormalizer(regionSizes, regionInfos);
267
268    // test when target region count is 8
269    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionCount())
270        .thenReturn(8);
271    assertThat(normalizer.computePlansForTable(tableName), contains(
272      new SplitNormalizationPlan(regionInfos.get(2), 60),
273      new SplitNormalizationPlan(regionInfos.get(3), 80)));
274
275    // test when target region count is 3
276    when(masterServices.getTableDescriptors().get(any()).getNormalizerTargetRegionCount())
277        .thenReturn(3);
278    assertThat(
279      normalizer.computePlansForTable(tableName),
280      contains(new MergeNormalizationPlan.Builder()
281        .addTarget(regionInfos.get(0), 20)
282        .addTarget(regionInfos.get(1), 40)
283        .build()));
284  }
285
286  @Test
287  public void testHonorsSplitEnabled() {
288    conf.setBoolean(SPLIT_ENABLED_KEY, true);
289    final TableName tableName = name.getTableName();
290    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5);
291    final Map<byte[], Integer> regionSizes =
292      createRegionSizesMap(regionInfos, 5, 5, 20, 5, 5);
293    setupMocksForNormalizer(regionSizes, regionInfos);
294    assertThat(
295      normalizer.computePlansForTable(tableName),
296      contains(instanceOf(SplitNormalizationPlan.class)));
297
298    conf.setBoolean(SPLIT_ENABLED_KEY, false);
299    setupMocksForNormalizer(regionSizes, regionInfos);
300    assertThat(normalizer.computePlansForTable(tableName), empty());
301  }
302
303  @Test
304  public void testHonorsMergeEnabled() {
305    conf.setBoolean(MERGE_ENABLED_KEY, true);
306    final TableName tableName = name.getTableName();
307    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5);
308    final Map<byte[], Integer> regionSizes =
309      createRegionSizesMap(regionInfos, 20, 5, 5, 20, 20);
310    setupMocksForNormalizer(regionSizes, regionInfos);
311    assertThat(
312      normalizer.computePlansForTable(tableName),
313      contains(instanceOf(MergeNormalizationPlan.class)));
314
315    conf.setBoolean(MERGE_ENABLED_KEY, false);
316    setupMocksForNormalizer(regionSizes, regionInfos);
317    assertThat(normalizer.computePlansForTable(tableName), empty());
318  }
319
320  @Test
321  public void testHonorsMinimumRegionCount() {
322    conf.setInt(MIN_REGION_COUNT_KEY, 1);
323    final TableName tableName = name.getTableName();
324    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 3);
325    // create a table topology that results in both a merge plan and a split plan. Assert that the
326    // merge is only created when the when the number of table regions is above the region count
327    // threshold, and that the split plan is create in both cases.
328    final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 1, 10);
329    setupMocksForNormalizer(regionSizes, regionInfos);
330
331    List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
332    assertThat(plans, contains(
333      new SplitNormalizationPlan(regionInfos.get(2), 10),
334      new MergeNormalizationPlan.Builder()
335        .addTarget(regionInfos.get(0), 1)
336        .addTarget(regionInfos.get(1), 1)
337        .build()));
338
339    // have to call setupMocks again because we don't have dynamic config update on normalizer.
340    conf.setInt(MIN_REGION_COUNT_KEY, 4);
341    setupMocksForNormalizer(regionSizes, regionInfos);
342    assertThat(normalizer.computePlansForTable(tableName), contains(
343      new SplitNormalizationPlan(regionInfos.get(2), 10)));
344  }
345
346  @Test
347  public void testHonorsMergeMinRegionAge() {
348    conf.setInt(MERGE_MIN_REGION_AGE_DAYS_KEY, 7);
349    final TableName tableName = name.getTableName();
350    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4);
351    final Map<byte[], Integer> regionSizes =
352      createRegionSizesMap(regionInfos, 1, 1, 10, 10);
353    setupMocksForNormalizer(regionSizes, regionInfos);
354    assertEquals(Period.ofDays(7), normalizer.getMergeMinRegionAge());
355    assertThat(
356      normalizer.computePlansForTable(tableName),
357      everyItem(not(instanceOf(MergeNormalizationPlan.class))));
358
359    // have to call setupMocks again because we don't have dynamic config update on normalizer.
360    conf.unset(MERGE_MIN_REGION_AGE_DAYS_KEY);
361    setupMocksForNormalizer(regionSizes, regionInfos);
362    assertEquals(
363      Period.ofDays(DEFAULT_MERGE_MIN_REGION_AGE_DAYS), normalizer.getMergeMinRegionAge());
364    final List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
365    assertThat(plans, not(empty()));
366    assertThat(plans, everyItem(instanceOf(MergeNormalizationPlan.class)));
367  }
368
369  @Test
370  public void testHonorsMergeMinRegionSize() {
371    conf.setBoolean(SPLIT_ENABLED_KEY, false);
372    final TableName tableName = name.getTableName();
373    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5);
374    final Map<byte[], Integer> regionSizes =
375      createRegionSizesMap(regionInfos, 1, 2, 0, 10, 10);
376    setupMocksForNormalizer(regionSizes, regionInfos);
377
378    assertFalse(normalizer.isSplitEnabled());
379    assertEquals(1, normalizer.getMergeMinRegionSizeMb());
380    assertThat(
381      normalizer.computePlansForTable(tableName),
382      contains(new MergeNormalizationPlan.Builder()
383        .addTarget(regionInfos.get(0), 1)
384        .addTarget(regionInfos.get(1), 2)
385        .build()));
386
387    conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 3);
388    setupMocksForNormalizer(regionSizes, regionInfos);
389    assertEquals(3, normalizer.getMergeMinRegionSizeMb());
390    assertThat(normalizer.computePlansForTable(tableName), empty());
391  }
392
393  @Test
394  public void testMergeEmptyRegions0() {
395    conf.setBoolean(SPLIT_ENABLED_KEY, false);
396    conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 0);
397    final TableName tableName = name.getTableName();
398    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 7);
399    final Map<byte[], Integer> regionSizes =
400      createRegionSizesMap(regionInfos, 0, 1, 10, 0, 9, 10, 0);
401    setupMocksForNormalizer(regionSizes, regionInfos);
402
403    assertFalse(normalizer.isSplitEnabled());
404    assertEquals(0, normalizer.getMergeMinRegionSizeMb());
405    assertThat(normalizer.computePlansForTable(tableName), contains(
406      new MergeNormalizationPlan.Builder()
407        .addTarget(regionInfos.get(0), 0)
408        .addTarget(regionInfos.get(1), 1)
409        .build(),
410      new MergeNormalizationPlan.Builder()
411        .addTarget(regionInfos.get(2), 10)
412        .addTarget(regionInfos.get(3), 0)
413        .build(),
414      new MergeNormalizationPlan.Builder()
415        .addTarget(regionInfos.get(5), 10)
416        .addTarget(regionInfos.get(6), 0)
417        .build()));
418  }
419
420  @Test
421  public void testMergeEmptyRegions1() {
422    conf.setBoolean(SPLIT_ENABLED_KEY, false);
423    conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 0);
424    final TableName tableName = name.getTableName();
425    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 8);
426    final Map<byte[], Integer> regionSizes =
427      createRegionSizesMap(regionInfos, 0, 1, 10, 0, 9, 0, 10, 0);
428    setupMocksForNormalizer(regionSizes, regionInfos);
429
430    assertFalse(normalizer.isSplitEnabled());
431    assertEquals(0, normalizer.getMergeMinRegionSizeMb());
432    assertThat(normalizer.computePlansForTable(tableName), contains(
433      new MergeNormalizationPlan.Builder()
434        .addTarget(regionInfos.get(0), 0)
435        .addTarget(regionInfos.get(1), 1)
436        .build(),
437      new MergeNormalizationPlan.Builder()
438        .addTarget(regionInfos.get(2), 10)
439        .addTarget(regionInfos.get(3), 0)
440        .build(),
441      new MergeNormalizationPlan.Builder()
442        .addTarget(regionInfos.get(4), 9)
443        .addTarget(regionInfos.get(5), 0)
444        .build(),
445      new MergeNormalizationPlan.Builder()
446        .addTarget(regionInfos.get(6), 10)
447        .addTarget(regionInfos.get(7), 0)
448        .build()));
449  }
450
451  @Test
452  public void testSplitAndMultiMerge() {
453    conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 0);
454    final TableName tableName = name.getTableName();
455    final List<RegionInfo> regionInfos = createRegionInfos(tableName, 8);
456    final Map<byte[], Integer> regionSizes =
457      createRegionSizesMap(regionInfos, 3, 1, 1, 30, 9, 3, 1, 0);
458    setupMocksForNormalizer(regionSizes, regionInfos);
459
460    assertTrue(normalizer.isMergeEnabled());
461    assertTrue(normalizer.isSplitEnabled());
462    assertEquals(0, normalizer.getMergeMinRegionSizeMb());
463    assertThat(normalizer.computePlansForTable(tableName), contains(
464      new SplitNormalizationPlan(regionInfos.get(3), 30),
465      new MergeNormalizationPlan.Builder()
466        .addTarget(regionInfos.get(0), 3)
467        .addTarget(regionInfos.get(1), 1)
468        .addTarget(regionInfos.get(2), 1)
469        .build(),
470      new MergeNormalizationPlan.Builder()
471        .addTarget(regionInfos.get(5), 3)
472        .addTarget(regionInfos.get(6), 1)
473        .addTarget(regionInfos.get(7), 0)
474        .build()));
475  }
476
477  // This test is to make sure that normalizer is only going to merge adjacent regions.
478  @Test
479  public void testNormalizerCannotMergeNonAdjacentRegions() {
480    final TableName tableName = name.getTableName();
481    // create 5 regions with sizes to trigger merge of small regions. region ranges are:
482    // [, "aa"), ["aa", "aa1"), ["aa1", "aa1!"), ["aa1!", "aa2"), ["aa2", )
483    // Region ["aa", "aa1") and ["aa1!", "aa2") are not adjacent, they are not supposed to
484    // merged.
485    final byte[][] keys = {
486      null,
487      Bytes.toBytes("aa"),
488      Bytes.toBytes("aa1!"),
489      Bytes.toBytes("aa1"),
490      Bytes.toBytes("aa2"),
491      null,
492    };
493    final List<RegionInfo> regionInfos = createRegionInfos(tableName, keys);
494    final Map<byte[], Integer> regionSizes =
495      createRegionSizesMap(regionInfos, 3, 1, 1, 3, 5);
496    setupMocksForNormalizer(regionSizes, regionInfos);
497
498    // Compute the plan, no merge plan returned as they are not adjacent.
499    List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
500    assertThat(plans, empty());
501  }
502
503  @SuppressWarnings("MockitoCast")
504  private void setupMocksForNormalizer(Map<byte[], Integer> regionSizes,
505    List<RegionInfo> regionInfoList) {
506    masterServices = Mockito.mock(MasterServices.class, RETURNS_DEEP_STUBS);
507
508    // for simplicity all regions are assumed to be on one server; doesn't matter to us
509    ServerName sn = ServerName.valueOf("localhost", 0, 0L);
510    when(masterServices.getAssignmentManager().getRegionStates()
511      .getRegionsOfTable(any())).thenReturn(regionInfoList);
512    when(masterServices.getAssignmentManager().getRegionStates()
513      .getRegionServerOfRegion(any())).thenReturn(sn);
514    when(masterServices.getAssignmentManager().getRegionStates()
515      .getRegionState(any(RegionInfo.class))).thenReturn(
516        RegionState.createForTesting(null, RegionState.State.OPEN));
517
518    for (Map.Entry<byte[], Integer> region : regionSizes.entrySet()) {
519      RegionMetrics regionLoad = Mockito.mock(RegionMetrics.class);
520      when(regionLoad.getRegionName()).thenReturn(region.getKey());
521      when(regionLoad.getStoreFileSize())
522        .thenReturn(new Size(region.getValue(), Size.Unit.MEGABYTE));
523
524      // this is possibly broken with jdk9, unclear if false positive or not
525      // suppress it for now, fix it when we get to running tests on 9
526      // see: http://errorprone.info/bugpattern/MockitoCast
527      when((Object) masterServices.getServerManager().getLoad(sn)
528        .getRegionMetrics().get(region.getKey())).thenReturn(regionLoad);
529    }
530
531    when(masterServices.isSplitOrMergeEnabled(any())).thenReturn(true);
532
533    normalizer = new SimpleRegionNormalizer();
534    normalizer.setConf(conf);
535    normalizer.setMasterServices(masterServices);
536  }
537
538  /**
539   * Create a list of {@link RegionInfo}s that represent a region chain of the specified length.
540   */
541  private static List<RegionInfo> createRegionInfos(final TableName tableName, final int length) {
542    if (length < 1) {
543      throw new IllegalStateException("length must be greater than or equal to 1.");
544    }
545
546    final byte[] startKey = Bytes.toBytes("aaaaa");
547    final byte[] endKey = Bytes.toBytes("zzzzz");
548    if (length == 1) {
549      return Collections.singletonList(createRegionInfo(tableName, startKey, endKey));
550    }
551
552    final byte[][] splitKeys = Bytes.split(startKey, endKey, length - 1);
553    final List<RegionInfo> ret = new ArrayList<>(length);
554    for (int i = 0; i < splitKeys.length - 1; i++) {
555      ret.add(createRegionInfo(tableName, splitKeys[i], splitKeys[i+1]));
556    }
557    return ret;
558  }
559
560  private static RegionInfo createRegionInfo(final TableName tableName, final byte[] startKey,
561    final byte[] endKey) {
562    return RegionInfoBuilder.newBuilder(tableName)
563      .setStartKey(startKey)
564      .setEndKey(endKey)
565      .setRegionId(generateRegionId())
566      .build();
567  }
568
569  private static long generateRegionId() {
570    return Instant.ofEpochMilli(EnvironmentEdgeManager.currentTime())
571      .minus(Period.ofDays(DEFAULT_MERGE_MIN_REGION_AGE_DAYS + 1))
572      .toEpochMilli();
573  }
574
575  private static List<RegionInfo> createRegionInfos(final TableName tableName,
576    final byte[][] splitKeys) {
577    final List<RegionInfo> ret = new ArrayList<>(splitKeys.length);
578    for (int i = 0; i < splitKeys.length - 1; i++) {
579      ret.add(createRegionInfo(tableName, splitKeys[i], splitKeys[i+1]));
580    }
581    return ret;
582  }
583
584  private static Map<byte[], Integer> createRegionSizesMap(final List<RegionInfo> regionInfos,
585    int... sizes) {
586    if (regionInfos.size() != sizes.length) {
587      throw new IllegalStateException("Parameter lengths must match.");
588    }
589
590    final Map<byte[], Integer> ret = new HashMap<>(regionInfos.size());
591    for (int i = 0; i < regionInfos.size(); i++) {
592      ret.put(regionInfos.get(i).getRegionName(), sizes[i]);
593    }
594    return ret;
595  }
596}