1   /**
2    * Copyright 2011 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.util;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Comparator;
28  import java.util.List;
29  import java.util.SortedSet;
30  import java.util.UUID;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.hbase.SmallTests;
35  import org.junit.Test;
36  
37  import com.google.common.collect.ComparisonChain;
38  import com.google.common.collect.Multimap;
39  
40  import org.junit.experimental.categories.Category;
41  
42  @Category(SmallTests.class)
43  public class TestRegionSplitCalculator {
44    private static final Log LOG = LogFactory.getLog(TestRegionSplitCalculator.class);
45  
46    /**
47     * This is range uses a user specified start and end keys. It also has an
48     * extra tiebreaker so that different ranges with the same start/end key pair
49     * count as different regions.
50     */
51    static class SimpleRange implements KeyRange {
52      byte[] start, end;
53      UUID tiebreaker;
54  
55      SimpleRange(byte[] start, byte[] end) {
56        this.start = start;
57        this.end = end;
58        this.tiebreaker = UUID.randomUUID();
59      }
60  
61      @Override
62      public byte[] getStartKey() {
63        return start;
64      }
65  
66      @Override
67      public byte[] getEndKey() {
68        return end;
69      }
70  
71      public String toString() {
72        return "[" + Bytes.toString(start) + ", " + Bytes.toString(end) + "]";
73      }
74    }
75  
76    Comparator<SimpleRange> cmp = new Comparator<SimpleRange>() {
77      @Override
78      public int compare(SimpleRange sr1, SimpleRange sr2) {
79        ComparisonChain cc = ComparisonChain.start();
80        cc = cc.compare(sr1.getStartKey(), sr2.getStartKey(),
81            Bytes.BYTES_COMPARATOR);
82        cc = cc.compare(sr1.getEndKey(), sr2.getEndKey(),
83            RegionSplitCalculator.BYTES_COMPARATOR);
84        cc = cc.compare(sr1.tiebreaker, sr2.tiebreaker);
85        return cc.result();
86      }
87    };
88  
89    /**
90     * Check the "depth" (number of regions included at a split) of a generated
91     * split calculation
92     */
93    void checkDepths(SortedSet<byte[]> splits,
94        Multimap<byte[], SimpleRange> regions, Integer... depths) {
95      assertEquals(splits.size(), depths.length);
96      int i = 0;
97      for (byte[] k : splits) {
98        Collection<SimpleRange> rs = regions.get(k);
99        int sz = rs == null ? 0 : rs.size();
100       assertEquals((int) depths[i], sz);
101       i++;
102     }
103   }
104 
105   /**
106    * This dumps data in a visually reasonable way for visual debugging. It has
107    * the basic iteration structure.
108    */
109   String dump(SortedSet<byte[]> splits, Multimap<byte[], SimpleRange> regions) {
110     // we display this way because the last end key should be displayed as well.
111     StringBuilder sb = new StringBuilder();
112     for (byte[] k : splits) {
113       sb.append(Bytes.toString(k)).append(":\t");
114       for (SimpleRange r : regions.get(k)) {
115         sb.append(r.toString()).append("\t");
116       }
117       sb.append("\n");
118     }
119     String s = sb.toString();
120     LOG.info("\n" + s);
121     return s;
122   }
123 
124   @Test
125   public void testSplitCalculator() {
126     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
127     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
128     SimpleRange c = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("D"));
129     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
130         cmp);
131     sc.add(a);
132     sc.add(b);
133     sc.add(c);
134 
135     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
136     LOG.info("Standard");
137     String res = dump(sc.getSplits(), regions);
138     checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
139     assertEquals(res, "A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t[C, D]\t\n"
140         + "D:\t\n");
141   }
142 
143   @Test
144   public void testSplitCalculatorNoEdge() {
145     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
146         cmp);
147 
148     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
149     LOG.info("Empty");
150     String res = dump(sc.getSplits(), regions);
151     checkDepths(sc.getSplits(), regions);
152     assertEquals("", res);
153   }
154 
155   @Test
156   public void testSplitCalculatorSingleEdge() {
157     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
158     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
159         cmp);
160     sc.add(a);
161 
162     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
163     LOG.info("Single edge");
164     String res = dump(sc.getSplits(), regions);
165     checkDepths(sc.getSplits(), regions, 1, 0);
166     assertEquals("A:\t[A, B]\t\n" + "B:\t\n", res);
167   }
168 
169   @Test
170   public void testSplitCalculatorDegenerateEdge() {
171     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("A"));
172     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
173         cmp);
174     sc.add(a);
175 
176     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
177     LOG.info("Single empty edge");
178     String res = dump(sc.getSplits(), regions);
179     checkDepths(sc.getSplits(), regions, 1);
180     assertEquals("A:\t[A, A]\t\n", res);
181   }
182 
183   @Test
184   public void testSplitCalculatorCoverSplit() {
185     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
186     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
187     SimpleRange c = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
188     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
189         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<SimpleRange>(
208         cmp);
209     sc.add(a);
210     sc.add(b);
211     sc.add(c);
212 
213     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
214     LOG.info("AB, BD covers BC");
215     String res = dump(sc.getSplits(), regions);
216     checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
217     assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t[B, D]\t\n"
218         + "C:\t[B, D]\t\n" + "D:\t\n", res);
219   }
220 
221   @Test
222   public void testSplitCalculatorHoles() {
223     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
224     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
225     SimpleRange c = new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("F"));
226     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
227         cmp);
228     sc.add(a);
229     sc.add(b);
230     sc.add(c);
231 
232     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
233     LOG.info("Hole between C and E");
234     String res = dump(sc.getSplits(), regions);
235     checkDepths(sc.getSplits(), regions, 1, 1, 0, 1, 0);
236     assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t\n"
237         + "E:\t[E, F]\t\n" + "F:\t\n", res);
238   }
239 
240   @Test
241   public void testSplitCalculatorOverreach() {
242     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
243     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D"));
244     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
245         cmp);
246     sc.add(a);
247     sc.add(b);
248 
249     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
250     LOG.info("AC and BD overlap but share no start/end keys");
251     String res = dump(sc.getSplits(), regions);
252     checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
253     assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, D]\t\n"
254         + "C:\t[B, D]\t\n" + "D:\t\n", res);
255   }
256 
257   @Test
258   public void testSplitCalculatorFloor() {
259     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
260     SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
261     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
262         cmp);
263     sc.add(a);
264     sc.add(b);
265 
266     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
267     LOG.info("AC and AB overlap in the beginning");
268     String res = dump(sc.getSplits(), regions);
269     checkDepths(sc.getSplits(), regions, 2, 1, 0);
270     assertEquals("A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t\n" + "C:\t\n", res);
271   }
272 
273   @Test
274   public void testSplitCalculatorCeil() {
275     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
276     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
277     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
278         cmp);
279     sc.add(a);
280     sc.add(b);
281 
282     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
283     LOG.info("AC and BC overlap in the end");
284     String res = dump(sc.getSplits(), regions);
285     checkDepths(sc.getSplits(), regions, 1, 2, 0);
286     assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n" + "C:\t\n", res);
287   }
288 
289   @Test
290   public void testSplitCalculatorEq() {
291     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
292     SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
293 
294     LOG.info(a.tiebreaker + " - " + b.tiebreaker);
295     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
296         cmp);
297     sc.add(a);
298     sc.add(b);
299 
300     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
301     LOG.info("AC and AC overlap completely");
302     String res = dump(sc.getSplits(), regions);
303     checkDepths(sc.getSplits(), regions, 2, 0);
304     assertEquals("A:\t[A, C]\t[A, C]\t\n" + "C:\t\n", res);
305   }
306 
307   @Test
308   public void testSplitCalculatorBackwards() {
309     SimpleRange a = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("A"));
310     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
311         cmp);
312     sc.add(a);
313 
314     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
315     LOG.info("CA is backwards");
316     String res = dump(sc.getSplits(), regions);
317     checkDepths(sc.getSplits(), regions); // expect nothing
318     assertEquals("", res);
319   }
320 
321   @Test
322   public void testComplex() {
323     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
324         cmp);
325     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("Am")));
326     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")));
327     sc.add(new SimpleRange(Bytes.toBytes("Am"), Bytes.toBytes("C")));
328     sc.add(new SimpleRange(Bytes.toBytes("D"), Bytes.toBytes("E")));
329     sc.add(new SimpleRange(Bytes.toBytes("F"), Bytes.toBytes("G")));
330     sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("E")));
331     sc.add(new SimpleRange(Bytes.toBytes("H"), Bytes.toBytes("I")));
332     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
333 
334     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
335     LOG.info("Something fairly complex");
336     String res = dump(sc.getSplits(), regions);
337     checkDepths(sc.getSplits(), regions, 3, 3, 3, 1, 2, 0, 1, 0, 1, 0);
338     assertEquals("A:\t[A, Am]\t[A, B]\t[A, C]\t\n"
339         + "Am:\t[A, B]\t[A, C]\t[Am, C]\t\n"
340         + "B:\t[A, C]\t[Am, C]\t[B, E]\t\n" + "C:\t[B, E]\t\n"
341         + "D:\t[B, E]\t[D, E]\t\n" + "E:\t\n" + "F:\t[F, G]\t\n" + "G:\t\n"
342         + "H:\t[H, I]\t\n" + "I:\t\n", res);
343   }
344 
345   @Test
346   public void testBeginEndMarker() {
347     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
348         cmp);
349     sc.add(new SimpleRange(Bytes.toBytes(""), Bytes.toBytes("A")));
350     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
351     sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("")));
352 
353     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
354     LOG.info("Special cases -- empty");
355     String res = dump(sc.getSplits(), regions);
356     checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
357     assertEquals(":\t[, A]\t\n" + "A:\t[A, B]\t\n" + "B:\t[B, ]\t\n"
358         + "null:\t\n", res);
359   }
360 
361   @Test
362   public void testBigRanges() {
363     SimpleRange ai = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("I"));
364     SimpleRange ae = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E"));
365     SimpleRange ac = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
366 
367     Collection<SimpleRange> bigOverlap = new ArrayList<SimpleRange>();
368     bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E")));
369     bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")));
370     bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
371     bigOverlap.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")));
372     bigOverlap.add(new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("H")));
373     bigOverlap.add(ai);
374     bigOverlap.add(ae);
375     bigOverlap.add(ac);
376 
377     // Expect 1 range to be returned: ai
378     List<SimpleRange> bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 1);
379     assertEquals(1, bigRanges.size());
380     assertEquals(ai, bigRanges.get(0));
381 
382     // Expect 3 ranges to be returned: ai, ae and ac
383     bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 3);
384     assertEquals(3, bigRanges.size());
385     assertEquals(ai, bigRanges.get(0));
386 
387     SimpleRange r1 = bigRanges.get(1);
388     SimpleRange r2 = bigRanges.get(2);
389     assertEquals("A", Bytes.toString(r1.start));
390     assertEquals("A", Bytes.toString(r2.start));
391     String r1e = Bytes.toString(r1.end);
392     String r2e = Bytes.toString(r2.end);
393     assertTrue((r1e.equals("C") && r2e.equals("E"))
394       || (r1e.equals("E") && r2e.equals("C")));
395   }
396 
397   @org.junit.Rule
398   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
399     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
400 }
401