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.util;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertTrue;
022
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Comparator;
026import java.util.List;
027import java.util.SortedSet;
028import java.util.UUID;
029import org.apache.hadoop.hbase.HBaseClassTestRule;
030import org.apache.hadoop.hbase.HBaseTestingUtility;
031import org.apache.hadoop.hbase.testclassification.MiscTests;
032import org.apache.hadoop.hbase.testclassification.SmallTests;
033import org.junit.ClassRule;
034import org.junit.Test;
035import org.junit.experimental.categories.Category;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import org.apache.hbase.thirdparty.com.google.common.collect.ComparisonChain;
040import org.apache.hbase.thirdparty.com.google.common.collect.Multimap;
041
042@Category({MiscTests.class, SmallTests.class})
043public class TestRegionSplitCalculator {
044
045  @ClassRule
046  public static final HBaseClassTestRule CLASS_RULE =
047      HBaseClassTestRule.forClass(TestRegionSplitCalculator.class);
048
049  private static final Logger LOG = LoggerFactory.getLogger(TestRegionSplitCalculator.class);
050  public static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
051  /**
052   * This is range uses a user specified start and end keys. It also has an
053   * extra tiebreaker so that different ranges with the same start/end key pair
054   * count as different regions.
055   */
056  static class SimpleRange implements KeyRange {
057    byte[] start, end;
058    UUID tiebreaker;
059
060    SimpleRange(byte[] start, byte[] end) {
061      this.start = start;
062      this.end = end;
063      this.tiebreaker = TEST_UTIL.getRandomUUID();
064    }
065
066    @Override
067    public byte[] getStartKey() {
068      return start;
069    }
070
071    @Override
072    public byte[] getEndKey() {
073      return end;
074    }
075
076    @Override
077    public String toString() {
078      return "[" + Bytes.toString(start) + ", " + Bytes.toString(end) + "]";
079    }
080  }
081
082  Comparator<SimpleRange> cmp = new Comparator<SimpleRange>() {
083    @Override
084    public int compare(SimpleRange sr1, SimpleRange sr2) {
085      ComparisonChain cc = ComparisonChain.start();
086      cc = cc.compare(sr1.getStartKey(), sr2.getStartKey(),
087          Bytes.BYTES_COMPARATOR);
088      cc = cc.compare(sr1.getEndKey(), sr2.getEndKey(),
089          RegionSplitCalculator.BYTES_COMPARATOR);
090      cc = cc.compare(sr1.tiebreaker, sr2.tiebreaker);
091      return cc.result();
092    }
093  };
094
095  /**
096   * Check the "depth" (number of regions included at a split) of a generated
097   * split calculation
098   */
099  void checkDepths(SortedSet<byte[]> splits,
100      Multimap<byte[], SimpleRange> regions, Integer... depths) {
101    assertEquals(splits.size(), depths.length);
102    int i = 0;
103    for (byte[] k : splits) {
104      Collection<SimpleRange> rs = regions.get(k);
105      int sz = rs == null ? 0 : rs.size();
106      assertEquals((int) depths[i], sz);
107      i++;
108    }
109  }
110
111  /**
112   * This dumps data in a visually reasonable way for visual debugging. It has
113   * the basic iteration structure.
114   */
115  String dump(SortedSet<byte[]> splits, Multimap<byte[], SimpleRange> regions) {
116    // we display this way because the last end key should be displayed as well.
117    StringBuilder sb = new StringBuilder();
118    for (byte[] k : splits) {
119      sb.append(Bytes.toString(k)).append(":\t");
120      for (SimpleRange r : regions.get(k)) {
121        sb.append(r.toString()).append("\t");
122      }
123      sb.append("\n");
124    }
125    String s = sb.toString();
126    LOG.info("\n" + s);
127    return s;
128  }
129
130  @Test
131  public void testSplitCalculator() {
132    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
133    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
134    SimpleRange c = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("D"));
135    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
136    sc.add(a);
137    sc.add(b);
138    sc.add(c);
139
140    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
141    LOG.info("Standard");
142    String res = dump(sc.getSplits(), regions);
143    checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
144    assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t[C, D]\t\nD:\t\n", res);
145  }
146
147  @Test
148  public void testSplitCalculatorNoEdge() {
149    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
150
151    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
152    LOG.info("Empty");
153    String res = dump(sc.getSplits(), regions);
154    checkDepths(sc.getSplits(), regions);
155    assertEquals("", res);
156  }
157
158  @Test
159  public void testSplitCalculatorSingleEdge() {
160    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
161    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
162    sc.add(a);
163
164    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
165    LOG.info("Single edge");
166    String res = dump(sc.getSplits(), regions);
167    checkDepths(sc.getSplits(), regions, 1, 0);
168    assertEquals("A:\t[A, B]\t\n" + "B:\t\n", res);
169  }
170
171  @Test
172  public void testSplitCalculatorDegenerateEdge() {
173    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("A"));
174    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
175    sc.add(a);
176
177    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
178    LOG.info("Single empty edge");
179    String res = dump(sc.getSplits(), regions);
180    checkDepths(sc.getSplits(), regions, 1);
181    assertEquals("A:\t[A, A]\t\n", res);
182  }
183
184  @Test
185  public void testSplitCalculatorCoverSplit() {
186    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
187    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
188    SimpleRange c = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
189    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
190    sc.add(a);
191    sc.add(b);
192    sc.add(c);
193
194    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
195    LOG.info("AC covers AB, BC");
196    String res = dump(sc.getSplits(), regions);
197    checkDepths(sc.getSplits(), regions, 2, 2, 0);
198    assertEquals("A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n"
199        + "C:\t\n", res);
200  }
201
202  @Test
203  public void testSplitCalculatorOverEndpoint() {
204    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
205    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
206    SimpleRange c = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D"));
207    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
208    sc.add(a);
209    sc.add(b);
210    sc.add(c);
211
212    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
213    LOG.info("AB, BD covers BC");
214    String res = dump(sc.getSplits(), regions);
215    checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
216    assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t[B, D]\t\n"
217        + "C:\t[B, D]\t\n" + "D:\t\n", res);
218  }
219
220  @Test
221  public void testSplitCalculatorHoles() {
222    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
223    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
224    SimpleRange c = new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("F"));
225    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
226    sc.add(a);
227    sc.add(b);
228    sc.add(c);
229
230    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
231    LOG.info("Hole between C and E");
232    String res = dump(sc.getSplits(), regions);
233    checkDepths(sc.getSplits(), regions, 1, 1, 0, 1, 0);
234    assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t\n"
235        + "E:\t[E, F]\t\n" + "F:\t\n", res);
236  }
237
238  @Test
239  public void testSplitCalculatorOverreach() {
240    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
241    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D"));
242    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
243    sc.add(a);
244    sc.add(b);
245
246    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
247    LOG.info("AC and BD overlap but share no start/end keys");
248    String res = dump(sc.getSplits(), regions);
249    checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
250    assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, D]\t\n"
251        + "C:\t[B, D]\t\n" + "D:\t\n", res);
252  }
253
254  @Test
255  public void testSplitCalculatorFloor() {
256    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
257    SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
258    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
259    sc.add(a);
260    sc.add(b);
261
262    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
263    LOG.info("AC and AB overlap in the beginning");
264    String res = dump(sc.getSplits(), regions);
265    checkDepths(sc.getSplits(), regions, 2, 1, 0);
266    assertEquals("A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t\n" + "C:\t\n", res);
267  }
268
269  @Test
270  public void testSplitCalculatorCeil() {
271    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
272    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
273    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
274    sc.add(a);
275    sc.add(b);
276
277    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
278    LOG.info("AC and BC overlap in the end");
279    String res = dump(sc.getSplits(), regions);
280    checkDepths(sc.getSplits(), regions, 1, 2, 0);
281    assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n" + "C:\t\n", res);
282  }
283
284  @Test
285  public void testSplitCalculatorEq() {
286    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
287    SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
288
289    LOG.info(a.tiebreaker + " - " + b.tiebreaker);
290    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
291    sc.add(a);
292    sc.add(b);
293
294    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
295    LOG.info("AC and AC overlap completely");
296    String res = dump(sc.getSplits(), regions);
297    checkDepths(sc.getSplits(), regions, 2, 0);
298    assertEquals("A:\t[A, C]\t[A, C]\t\n" + "C:\t\n", res);
299  }
300
301  @Test
302  public void testSplitCalculatorBackwards() {
303    SimpleRange a = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("A"));
304    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
305    sc.add(a);
306
307    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
308    LOG.info("CA is backwards");
309    String res = dump(sc.getSplits(), regions);
310    checkDepths(sc.getSplits(), regions); // expect nothing
311    assertEquals("", res);
312  }
313
314  @Test
315  public void testComplex() {
316    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
317    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("Am")));
318    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")));
319    sc.add(new SimpleRange(Bytes.toBytes("Am"), Bytes.toBytes("C")));
320    sc.add(new SimpleRange(Bytes.toBytes("D"), Bytes.toBytes("E")));
321    sc.add(new SimpleRange(Bytes.toBytes("F"), Bytes.toBytes("G")));
322    sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("E")));
323    sc.add(new SimpleRange(Bytes.toBytes("H"), Bytes.toBytes("I")));
324    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
325
326    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
327    LOG.info("Something fairly complex");
328    String res = dump(sc.getSplits(), regions);
329    checkDepths(sc.getSplits(), regions, 3, 3, 3, 1, 2, 0, 1, 0, 1, 0);
330    assertEquals("A:\t[A, Am]\t[A, B]\t[A, C]\t\n"
331        + "Am:\t[A, B]\t[A, C]\t[Am, C]\t\n"
332        + "B:\t[A, C]\t[Am, C]\t[B, E]\t\n" + "C:\t[B, E]\t\n"
333        + "D:\t[B, E]\t[D, E]\t\n" + "E:\t\n" + "F:\t[F, G]\t\n" + "G:\t\n"
334        + "H:\t[H, I]\t\n" + "I:\t\n", res);
335  }
336
337  @Test
338  public void testBeginEndMarker() {
339    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
340    sc.add(new SimpleRange(Bytes.toBytes(""), Bytes.toBytes("A")));
341    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
342    sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("")));
343
344    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
345    LOG.info("Special cases -- empty");
346    String res = dump(sc.getSplits(), regions);
347    checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
348    assertEquals(":\t[, A]\t\n" + "A:\t[A, B]\t\n" + "B:\t[B, ]\t\n"
349        + "null:\t\n", res);
350  }
351
352  @Test
353  public void testBigRanges() {
354    SimpleRange ai = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("I"));
355    SimpleRange ae = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E"));
356    SimpleRange ac = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
357
358    Collection<SimpleRange> bigOverlap = new ArrayList<>(8);
359    bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E")));
360    bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")));
361    bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
362    bigOverlap.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")));
363    bigOverlap.add(new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("H")));
364    bigOverlap.add(ai);
365    bigOverlap.add(ae);
366    bigOverlap.add(ac);
367
368    // Expect 1 range to be returned: ai
369    List<SimpleRange> bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 1);
370    assertEquals(1, bigRanges.size());
371    assertEquals(ai, bigRanges.get(0));
372
373    // Expect 3 ranges to be returned: ai, ae and ac
374    bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 3);
375    assertEquals(3, bigRanges.size());
376    assertEquals(ai, bigRanges.get(0));
377
378    SimpleRange r1 = bigRanges.get(1);
379    SimpleRange r2 = bigRanges.get(2);
380    assertEquals("A", Bytes.toString(r1.start));
381    assertEquals("A", Bytes.toString(r2.start));
382    String r1e = Bytes.toString(r1.end);
383    String r2e = Bytes.toString(r2.end);
384    assertTrue((r1e.equals("C") && r2e.equals("E"))
385      || (r1e.equals("E") && r2e.equals("C")));
386  }
387
388}
389