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  
24  import java.util.Collection;
25  import java.util.Comparator;
26  import java.util.SortedSet;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.junit.Test;
31  
32  import com.google.common.collect.ComparisonChain;
33  import com.google.common.collect.Multimap;
34  
35  public class TestRegionSplitCalculator {
36    final static Log LOG = LogFactory.getLog(TestRegionSplitCalculator.class);
37  
38    /**
39     * This is range uses a user specified start and end keys. It also has an
40     * extra time based tiebreaker so that different ranges with the same
41     * start/end key pair count as different regions.
42     */
43    static class SimpleRange implements KeyRange {
44      byte[] start, end;
45      long tiebreaker;
46  
47      SimpleRange(byte[] start, byte[] end) {
48        this.start = start;
49        this.end = end;
50        this.tiebreaker = System.nanoTime();
51      }
52  
53      @Override
54      public byte[] getStartKey() {
55        return start;
56      }
57  
58      @Override
59      public byte[] getEndKey() {
60        return end;
61      }
62  
63      public String toString() {
64        return "[" + Bytes.toString(start) + ", " + Bytes.toString(end) + "]";
65      }
66    }
67  
68    Comparator<SimpleRange> cmp = new Comparator<SimpleRange>() {
69      @Override
70      public int compare(SimpleRange sr1, SimpleRange sr2) {
71        ComparisonChain cc = ComparisonChain.start();
72        cc = cc.compare(sr1.getStartKey(), sr2.getStartKey(),
73            Bytes.BYTES_COMPARATOR);
74        cc = cc.compare(sr1.getEndKey(), sr2.getEndKey(),
75            RegionSplitCalculator.BYTES_COMPARATOR);
76        cc = cc.compare(sr1.tiebreaker, sr2.tiebreaker);
77        return cc.result();
78      }
79    };
80  
81    /**
82     * Check the "depth" (number of regions included at a split) of a generated
83     * split calculation
84     */
85    void checkDepths(SortedSet<byte[]> splits,
86        Multimap<byte[], SimpleRange> regions, Integer... depths) {
87      assertEquals(splits.size(), depths.length);
88      int i = 0;
89      for (byte[] k : splits) {
90        Collection<SimpleRange> rs = regions.get(k);
91        int sz = rs == null ? 0 : rs.size();
92        assertEquals((int) depths[i], sz);
93        i++;
94      }
95    }
96  
97    /**
98     * This dumps data in a visually reasonable way for visual debugging. It has
99     * the basic iteration structure.
100    */
101   String dump(SortedSet<byte[]> splits, Multimap<byte[], SimpleRange> regions) {
102     // we display this way because the last end key should be displayed as well.
103     StringBuilder sb = new StringBuilder();
104     for (byte[] k : splits) {
105       sb.append(Bytes.toString(k) + ":\t");
106       for (SimpleRange r : regions.get(k)) {
107         sb.append(r.toString() + "\t");
108       }
109       sb.append("\n");
110     }
111     String s = sb.toString();
112     LOG.info("\n" + s);
113     return s;
114   }
115 
116   @Test
117   public void testSplitCalculator() {
118     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
119     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
120     SimpleRange c = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("D"));
121     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
122         cmp);
123     sc.add(a);
124     sc.add(b);
125     sc.add(c);
126 
127     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
128     LOG.info("Standard");
129     String res = dump(sc.getSplits(), regions);
130     checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
131     assertEquals(res, "A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t[C, D]\t\n"
132         + "D:\t\n");
133   }
134 
135   @Test
136   public void testSplitCalculatorNoEdge() {
137     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
138         cmp);
139 
140     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
141     LOG.info("Empty");
142     String res = dump(sc.getSplits(), regions);
143     checkDepths(sc.getSplits(), regions);
144     assertEquals(res, "");
145   }
146 
147   @Test
148   public void testSplitCalculatorSingleEdge() {
149     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
150     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
151         cmp);
152     sc.add(a);
153 
154     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
155     LOG.info("Single edge");
156     String res = dump(sc.getSplits(), regions);
157     checkDepths(sc.getSplits(), regions, 1, 0);
158     assertEquals(res, "A:\t[A, B]\t\n" + "B:\t\n");
159   }
160 
161   @Test
162   public void testSplitCalculatorDegenerateEdge() {
163     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("A"));
164     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
165         cmp);
166     sc.add(a);
167 
168     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
169     LOG.info("Single empty edge");
170     String res = dump(sc.getSplits(), regions);
171     checkDepths(sc.getSplits(), regions, 1);
172     assertEquals(res, "A:\t[A, A]\t\n");
173   }
174 
175   @Test
176   public void testSplitCalculatorCoverSplit() {
177     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
178     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
179     SimpleRange c = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
180     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
181         cmp);
182     sc.add(a);
183     sc.add(b);
184     sc.add(c);
185 
186     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
187     LOG.info("AC covers AB, BC");
188     String res = dump(sc.getSplits(), regions);
189     checkDepths(sc.getSplits(), regions, 2, 2, 0);
190     assertEquals(res, "A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n"
191         + "C:\t\n");
192   }
193 
194   @Test
195   public void testSplitCalculatorOverEndpoint() {
196     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
197     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
198     SimpleRange c = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D"));
199     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
200         cmp);
201     sc.add(a);
202     sc.add(b);
203     sc.add(c);
204 
205     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
206     LOG.info("AB, BD covers BC");
207     String res = dump(sc.getSplits(), regions);
208     checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
209     assertEquals(res, "A:\t[A, B]\t\n" + "B:\t[B, C]\t[B, D]\t\n"
210         + "C:\t[B, D]\t\n" + "D:\t\n");
211   }
212 
213   @Test
214   public void testSplitCalculatorHoles() {
215     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
216     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
217     SimpleRange c = new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("F"));
218     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
219         cmp);
220     sc.add(a);
221     sc.add(b);
222     sc.add(c);
223 
224     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
225     LOG.info("Hole between C and E");
226     String res = dump(sc.getSplits(), regions);
227     checkDepths(sc.getSplits(), regions, 1, 1, 0, 1, 0);
228     assertEquals(res, "A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t\n"
229         + "E:\t[E, F]\t\n" + "F:\t\n");
230   }
231 
232   @Test
233   public void testSplitCalculatorOverreach() {
234     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
235     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D"));
236     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
237         cmp);
238     sc.add(a);
239     sc.add(b);
240 
241     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
242     LOG.info("AC and BD overlap but share no start/end keys");
243     String res = dump(sc.getSplits(), regions);
244     checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
245     assertEquals(res, "A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, D]\t\n"
246         + "C:\t[B, D]\t\n" + "D:\t\n");
247   }
248 
249   @Test
250   public void testSplitCalculatorFloor() {
251     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
252     SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
253     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
254         cmp);
255     sc.add(a);
256     sc.add(b);
257 
258     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
259     LOG.info("AC and AB overlap in the beginning");
260     String res = dump(sc.getSplits(), regions);
261     checkDepths(sc.getSplits(), regions, 2, 1, 0);
262     assertEquals(res, "A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t\n" + "C:\t\n");
263   }
264 
265   @Test
266   public void testSplitCalculatorCeil() {
267     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
268     SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
269     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
270         cmp);
271     sc.add(a);
272     sc.add(b);
273 
274     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
275     LOG.info("AC and BC overlap in the end");
276     String res = dump(sc.getSplits(), regions);
277     checkDepths(sc.getSplits(), regions, 1, 2, 0);
278     assertEquals(res, "A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n" + "C:\t\n");
279   }
280 
281   @Test
282   public void testSplitCalculatorEq() {
283     SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
284     SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
285     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
286         cmp);
287     sc.add(a);
288     sc.add(b);
289 
290     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
291     LOG.info("AC and AC overlap completely");
292     String res = dump(sc.getSplits(), regions);
293     checkDepths(sc.getSplits(), regions, 2, 0);
294     assertEquals(res, "A:\t[A, C]\t[A, C]\t\n" + "C:\t\n");
295   }
296 
297   @Test
298   public void testSplitCalculatorBackwards() {
299     SimpleRange a = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("A"));
300     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
301         cmp);
302     sc.add(a);
303 
304     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
305     LOG.info("CA is backwards");
306     String res = dump(sc.getSplits(), regions);
307     checkDepths(sc.getSplits(), regions); // expect nothing
308     assertEquals(res, "");
309   }
310 
311   @Test
312   public void testComplex() {
313     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
314         cmp);
315     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("Am")));
316     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")));
317     sc.add(new SimpleRange(Bytes.toBytes("Am"), Bytes.toBytes("C")));
318     sc.add(new SimpleRange(Bytes.toBytes("D"), Bytes.toBytes("E")));
319     sc.add(new SimpleRange(Bytes.toBytes("F"), Bytes.toBytes("G")));
320     sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("E")));
321     sc.add(new SimpleRange(Bytes.toBytes("H"), Bytes.toBytes("I")));
322     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
323 
324     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
325     LOG.info("Something fairly complex");
326     String res = dump(sc.getSplits(), regions);
327     checkDepths(sc.getSplits(), regions, 3, 3, 3, 1, 2, 0, 1, 0, 1, 0);
328     assertEquals(res, "A:\t[A, Am]\t[A, B]\t[A, C]\t\n"
329         + "Am:\t[A, B]\t[A, C]\t[Am, C]\t\n"
330         + "B:\t[A, C]\t[Am, C]\t[B, E]\t\n" + "C:\t[B, E]\t\n"
331         + "D:\t[B, E]\t[D, E]\t\n" + "E:\t\n" + "F:\t[F, G]\t\n" + "G:\t\n"
332         + "H:\t[H, I]\t\n" + "I:\t\n");
333   }
334 
335   @Test
336   public void testBeginEndMarker() {
337     RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<SimpleRange>(
338         cmp);
339     sc.add(new SimpleRange(Bytes.toBytes(""), Bytes.toBytes("A")));
340     sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
341     sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("")));
342 
343     Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
344     LOG.info("Special cases -- empty");
345     String res = dump(sc.getSplits(), regions);
346     checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
347     assertEquals(res, ":\t[, A]\t\n" + "A:\t[A, B]\t\n" + "B:\t[B, ]\t\n"
348         + "null:\t\n");
349   }
350 }