View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.util;
20  
21  import static org.junit.Assert.assertArrayEquals;
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotSame;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.commons.lang.ArrayUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.hbase.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.testclassification.MediumTests;
38  import org.apache.hadoop.hbase.ServerName;
39  import org.apache.hadoop.hbase.TableName;
40  import org.apache.hadoop.hbase.client.HTable;
41  import org.apache.hadoop.hbase.util.RegionSplitter.HexStringSplit;
42  import org.apache.hadoop.hbase.util.RegionSplitter.DecimalStringSplit;
43  import org.apache.hadoop.hbase.util.RegionSplitter.SplitAlgorithm;
44  import org.apache.hadoop.hbase.util.RegionSplitter.UniformSplit;
45  import org.junit.AfterClass;
46  import org.junit.BeforeClass;
47  import org.junit.Test;
48  import org.junit.experimental.categories.Category;
49  
50  /**
51   * Tests for {@link RegionSplitter}, which can create a pre-split table or do a
52   * rolling split of an existing table.
53   */
54  @Category(MediumTests.class)
55  public class TestRegionSplitter {
56      private final static Log LOG = LogFactory.getLog(TestRegionSplitter.class);
57      private final static HBaseTestingUtility UTIL = new HBaseTestingUtility();
58      private final static String CF_NAME = "SPLIT_TEST_CF";
59      private final static byte xFF = (byte) 0xff;
60  
61      @BeforeClass
62      public static void setup() throws Exception {
63          UTIL.startMiniCluster();
64      }
65  
66      @AfterClass
67      public static void teardown() throws Exception {
68          UTIL.shutdownMiniCluster();
69      }
70  
71      /**
72       * Test creating a pre-split table using the HexStringSplit algorithm.
73       */
74      @Test
75      public void testCreatePresplitTableHex() throws Exception {
76        final List<byte[]> expectedBounds = new ArrayList<byte[]>();
77        expectedBounds.add(ArrayUtils.EMPTY_BYTE_ARRAY);
78        expectedBounds.add("10000000".getBytes());
79        expectedBounds.add("20000000".getBytes());
80        expectedBounds.add("30000000".getBytes());
81        expectedBounds.add("40000000".getBytes());
82        expectedBounds.add("50000000".getBytes());
83        expectedBounds.add("60000000".getBytes());
84        expectedBounds.add("70000000".getBytes());
85        expectedBounds.add("80000000".getBytes());
86        expectedBounds.add("90000000".getBytes());
87        expectedBounds.add("a0000000".getBytes());
88        expectedBounds.add("b0000000".getBytes());
89        expectedBounds.add("c0000000".getBytes());
90        expectedBounds.add("d0000000".getBytes());
91        expectedBounds.add("e0000000".getBytes());
92        expectedBounds.add("f0000000".getBytes());
93            expectedBounds.add(ArrayUtils.EMPTY_BYTE_ARRAY);
94  
95            // Do table creation/pre-splitting and verification of region boundaries
96      preSplitTableAndVerify(expectedBounds,
97          HexStringSplit.class.getSimpleName(),
98          TableName.valueOf("NewHexPresplitTable"));
99      }
100 
101     /**
102      * Test creating a pre-split table using the UniformSplit algorithm.
103      */
104     @Test
105     public void testCreatePresplitTableUniform() throws Exception {
106       List<byte[]> expectedBounds = new ArrayList<byte[]>();
107       expectedBounds.add(ArrayUtils.EMPTY_BYTE_ARRAY);
108       expectedBounds.add(new byte[] {      0x10, 0, 0, 0, 0, 0, 0, 0});
109       expectedBounds.add(new byte[] {      0x20, 0, 0, 0, 0, 0, 0, 0});
110       expectedBounds.add(new byte[] {      0x30, 0, 0, 0, 0, 0, 0, 0});
111       expectedBounds.add(new byte[] {      0x40, 0, 0, 0, 0, 0, 0, 0});
112       expectedBounds.add(new byte[] {      0x50, 0, 0, 0, 0, 0, 0, 0});
113       expectedBounds.add(new byte[] {      0x60, 0, 0, 0, 0, 0, 0, 0});
114       expectedBounds.add(new byte[] {      0x70, 0, 0, 0, 0, 0, 0, 0});
115       expectedBounds.add(new byte[] {(byte)0x80, 0, 0, 0, 0, 0, 0, 0});
116       expectedBounds.add(new byte[] {(byte)0x90, 0, 0, 0, 0, 0, 0, 0});
117       expectedBounds.add(new byte[] {(byte)0xa0, 0, 0, 0, 0, 0, 0, 0});
118       expectedBounds.add(new byte[] {(byte)0xb0, 0, 0, 0, 0, 0, 0, 0});
119       expectedBounds.add(new byte[] {(byte)0xc0, 0, 0, 0, 0, 0, 0, 0});
120       expectedBounds.add(new byte[] {(byte)0xd0, 0, 0, 0, 0, 0, 0, 0});
121       expectedBounds.add(new byte[] {(byte)0xe0, 0, 0, 0, 0, 0, 0, 0});
122       expectedBounds.add(new byte[] {(byte)0xf0, 0, 0, 0, 0, 0, 0, 0});
123       expectedBounds.add(ArrayUtils.EMPTY_BYTE_ARRAY);
124 
125       // Do table creation/pre-splitting and verification of region boundaries
126       preSplitTableAndVerify(expectedBounds, UniformSplit.class.getSimpleName(),
127         TableName.valueOf("NewUniformPresplitTable"));
128     }
129 
130     /**
131      * Unit tests for the HexStringSplit algorithm. Makes sure it divides up the
132      * space of keys in the way that we expect.
133      */
134     @Test
135     public void unitTestHexStringSplit() {
136         HexStringSplit splitter = new HexStringSplit();
137         // Check splitting while starting from scratch
138 
139         byte[][] twoRegionsSplits = splitter.split(2);
140         assertEquals(1, twoRegionsSplits.length);
141         assertArrayEquals("80000000".getBytes(), twoRegionsSplits[0]);
142 
143         byte[][] threeRegionsSplits = splitter.split(3);
144         assertEquals(2, threeRegionsSplits.length);
145         byte[] expectedSplit0 = "55555555".getBytes();
146         assertArrayEquals(expectedSplit0, threeRegionsSplits[0]);
147         byte[] expectedSplit1 = "aaaaaaaa".getBytes();
148         assertArrayEquals(expectedSplit1, threeRegionsSplits[1]);
149 
150         // Check splitting existing regions that have start and end points
151         byte[] splitPoint = splitter.split("10000000".getBytes(), "30000000".getBytes());
152         assertArrayEquals("20000000".getBytes(), splitPoint);
153 
154         byte[] lastRow = "ffffffff".getBytes();
155         assertArrayEquals(lastRow, splitter.lastRow());
156         byte[] firstRow = "00000000".getBytes();
157         assertArrayEquals(firstRow, splitter.firstRow());
158 
159         // Halfway between 00... and 20... should be 10...
160         splitPoint = splitter.split(firstRow, "20000000".getBytes());
161         assertArrayEquals("10000000".getBytes(), splitPoint);
162 
163         // Halfway between df... and ff... should be ef....
164         splitPoint = splitter.split("dfffffff".getBytes(), lastRow);
165         assertArrayEquals(splitPoint,"efffffff".getBytes());
166 
167         // Check splitting region with multiple mappers per region
168         byte[][] splits = splitter.split("00000000".getBytes(), "30000000".getBytes(), 3, false);
169         assertEquals(2, splits.length);
170         assertArrayEquals(splits[0], "10000000".getBytes());
171         assertArrayEquals(splits[1], "20000000".getBytes());
172 
173         splits = splitter.split("00000000".getBytes(), "20000000".getBytes(), 2, true);
174         assertEquals(3, splits.length);
175         assertArrayEquals(splits[1], "10000000".getBytes());
176     }
177 
178     /**
179      * Unit tests for the DecimalStringSplit algorithm. Makes sure it divides up the
180      * space of keys in the way that we expect.
181      */
182     @Test
183     public void unitTestDecimalStringSplit() {
184         DecimalStringSplit splitter = new DecimalStringSplit();
185         // Check splitting while starting from scratch
186 
187         byte[][] twoRegionsSplits = splitter.split(2);
188         assertEquals(1, twoRegionsSplits.length);
189         assertArrayEquals("50000000".getBytes(), twoRegionsSplits[0]);
190 
191         byte[][] threeRegionsSplits = splitter.split(3);
192         assertEquals(2, threeRegionsSplits.length);
193         byte[] expectedSplit0 = "33333333".getBytes();
194         assertArrayEquals(expectedSplit0, threeRegionsSplits[0]);
195         byte[] expectedSplit1 = "66666666".getBytes();
196         assertArrayEquals(expectedSplit1, threeRegionsSplits[1]);
197 
198         // Check splitting existing regions that have start and end points
199         byte[] splitPoint = splitter.split("10000000".getBytes(), "30000000".getBytes());
200         assertArrayEquals("20000000".getBytes(), splitPoint);
201 
202         byte[] lastRow = "99999999".getBytes();
203         assertArrayEquals(lastRow, splitter.lastRow());
204         byte[] firstRow = "00000000".getBytes();
205         assertArrayEquals(firstRow, splitter.firstRow());
206 
207         // Halfway between 00... and 20... should be 10...
208         splitPoint = splitter.split(firstRow, "20000000".getBytes());
209         assertArrayEquals("10000000".getBytes(), splitPoint);
210 
211         // Halfway between 00... and 19... should be 09...
212         splitPoint = splitter.split(firstRow, "19999999".getBytes());
213         assertArrayEquals("09999999".getBytes(), splitPoint);
214 
215         // Halfway between 79... and 99... should be 89....
216         splitPoint = splitter.split("79999999".getBytes(), lastRow);
217         assertArrayEquals("89999999".getBytes(), splitPoint);
218     }
219 
220     /**
221      * Unit tests for the UniformSplit algorithm. Makes sure it divides up the space of
222      * keys in the way that we expect.
223      */
224     @Test
225     public void unitTestUniformSplit() {
226         UniformSplit splitter = new UniformSplit();
227 
228         // Check splitting while starting from scratch
229         try {
230             splitter.split(1);
231             throw new AssertionError("Splitting into <2 regions should have thrown exception");
232         } catch (IllegalArgumentException e) { }
233 
234         byte[][] twoRegionsSplits = splitter.split(2);
235         assertEquals(1, twoRegionsSplits.length);
236         assertArrayEquals(twoRegionsSplits[0],
237             new byte[] { (byte) 0x80, 0, 0, 0, 0, 0, 0, 0 });
238 
239         byte[][] threeRegionsSplits = splitter.split(3);
240         assertEquals(2, threeRegionsSplits.length);
241         byte[] expectedSplit0 = new byte[] {0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55};
242         assertArrayEquals(expectedSplit0, threeRegionsSplits[0]);
243         byte[] expectedSplit1 = new byte[] {(byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA,
244                 (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA};
245         assertArrayEquals(expectedSplit1, threeRegionsSplits[1]);
246 
247         // Check splitting existing regions that have start and end points
248         byte[] splitPoint = splitter.split(new byte[] {0x10}, new byte[] {0x30});
249         assertArrayEquals(new byte[] {0x20}, splitPoint);
250 
251         byte[] lastRow = new byte[] {xFF, xFF, xFF, xFF, xFF, xFF, xFF, xFF};
252         assertArrayEquals(lastRow, splitter.lastRow());
253         byte[] firstRow = ArrayUtils.EMPTY_BYTE_ARRAY;
254         assertArrayEquals(firstRow, splitter.firstRow());
255 
256         splitPoint = splitter.split(firstRow, new byte[] {0x20});
257         assertArrayEquals(splitPoint, new byte[] {0x10});
258 
259         splitPoint = splitter.split(new byte[] {(byte)0xdf, xFF, xFF, xFF, xFF,
260                 xFF, xFF, xFF}, lastRow);
261         assertArrayEquals(splitPoint,
262                 new byte[] {(byte)0xef, xFF, xFF, xFF, xFF, xFF, xFF, xFF});
263 
264         splitPoint = splitter.split(new byte[] {'a', 'a', 'a'}, new byte[] {'a', 'a', 'b'});
265         assertArrayEquals(splitPoint, new byte[] {'a', 'a', 'a', (byte)0x80 });
266 
267         // Check splitting region with multiple mappers per region
268         byte[][] splits = splitter.split(new byte[] {'a', 'a', 'a'}, new byte[] {'a', 'a', 'd'}, 3, false);
269         assertEquals(2, splits.length);
270         assertArrayEquals(splits[0], new byte[]{'a', 'a', 'b'});
271         assertArrayEquals(splits[1], new byte[]{'a', 'a', 'c'});
272 
273         splits = splitter.split(new byte[] {'a', 'a', 'a'}, new byte[] {'a', 'a', 'e'}, 2, true);
274         assertEquals(3, splits.length);
275         assertArrayEquals(splits[1], new byte[] { 'a', 'a', 'c'});
276     }
277 
278   @Test
279   public void testUserInput() {
280     SplitAlgorithm algo = new HexStringSplit();
281     assertFalse(splitFailsPrecondition(algo)); // default settings are fine
282     assertFalse(splitFailsPrecondition(algo, "00", "AA")); // custom is fine
283     assertTrue(splitFailsPrecondition(algo, "AA", "00")); // range error
284     assertTrue(splitFailsPrecondition(algo, "AA", "AA")); // range error
285     assertFalse(splitFailsPrecondition(algo, "0", "2", 3)); // should be fine
286     assertFalse(splitFailsPrecondition(algo, "0", "A", 11)); // should be fine
287     assertTrue(splitFailsPrecondition(algo, "0", "A", 12)); // too granular
288 
289     algo = new DecimalStringSplit();
290     assertFalse(splitFailsPrecondition(algo)); // default settings are fine
291     assertFalse(splitFailsPrecondition(algo, "00", "99")); // custom is fine
292     assertTrue(splitFailsPrecondition(algo, "99", "00")); // range error
293     assertTrue(splitFailsPrecondition(algo, "99", "99")); // range error
294     assertFalse(splitFailsPrecondition(algo, "0", "2", 3)); // should be fine
295     assertFalse(splitFailsPrecondition(algo, "0", "9", 10)); // should be fine
296     assertTrue(splitFailsPrecondition(algo, "0", "9", 11)); // too granular
297 
298     algo = new UniformSplit();
299     assertFalse(splitFailsPrecondition(algo)); // default settings are fine
300     assertFalse(splitFailsPrecondition(algo, "\\x00", "\\xAA")); // custom is fine
301     assertTrue(splitFailsPrecondition(algo, "\\xAA", "\\x00")); // range error
302     assertTrue(splitFailsPrecondition(algo, "\\xAA", "\\xAA")); // range error
303     assertFalse(splitFailsPrecondition(algo, "\\x00", "\\x02", 3)); // should be fine
304     assertFalse(splitFailsPrecondition(algo, "\\x00", "\\x0A", 11)); // should be fine
305     assertFalse(splitFailsPrecondition(algo, "\\x00", "\\x0A", 12)); // should be fine
306   }
307 
308   private boolean splitFailsPrecondition(SplitAlgorithm algo) {
309     return splitFailsPrecondition(algo, 100);
310   }
311 
312   private boolean splitFailsPrecondition(SplitAlgorithm algo, String firstRow,
313       String lastRow) {
314     return splitFailsPrecondition(algo, firstRow, lastRow, 100);
315   }
316 
317   private boolean splitFailsPrecondition(SplitAlgorithm algo, String firstRow,
318       String lastRow, int numRegions) {
319     algo.setFirstRow(firstRow);
320     algo.setLastRow(lastRow);
321     return splitFailsPrecondition(algo, numRegions);
322   }
323 
324   private boolean splitFailsPrecondition(SplitAlgorithm algo, int numRegions) {
325     try {
326       byte[][] s = algo.split(numRegions);
327       LOG.debug("split algo = " + algo);
328       if (s != null) {
329         StringBuilder sb = new StringBuilder();
330         for (byte[] b : s) {
331           sb.append(Bytes.toStringBinary(b) + "  ");
332         }
333         LOG.debug(sb.toString());
334       }
335       return false;
336     } catch (IllegalArgumentException e) {
337       return true;
338     } catch (IllegalStateException e) {
339       return true;
340     } catch (IndexOutOfBoundsException e) {
341       return true;
342     }
343   }
344 
345     /**
346      * Creates a pre-split table with expectedBounds.size()+1 regions, then
347      * verifies that the region boundaries are the same as the expected
348      * region boundaries in expectedBounds.
349      * @throws Various junit assertions
350      */
351     private void preSplitTableAndVerify(List<byte[]> expectedBounds,
352             String splitClass, TableName tableName) throws Exception {
353         final int numRegions = expectedBounds.size()-1;
354         final Configuration conf = UTIL.getConfiguration();
355         conf.setInt("split.count", numRegions);
356         SplitAlgorithm splitAlgo = RegionSplitter.newSplitAlgoInstance(conf, splitClass);
357         RegionSplitter.createPresplitTable(tableName, splitAlgo, new String[] {CF_NAME}, conf);
358         verifyBounds(expectedBounds, tableName);
359     }
360 
361   @Test
362   public void noopRollingSplit() throws Exception {
363     final List<byte[]> expectedBounds = new ArrayList<byte[]>();
364     expectedBounds.add(ArrayUtils.EMPTY_BYTE_ARRAY);
365     rollingSplitAndVerify(
366         TableName.valueOf(TestRegionSplitter.class.getSimpleName()),
367         "UniformSplit", expectedBounds);
368   }
369 
370     private void rollingSplitAndVerify(TableName tableName, String splitClass,
371             List<byte[]> expectedBounds)  throws Exception {
372         final Configuration conf = UTIL.getConfiguration();
373 
374         // Set this larger than the number of splits so RegionSplitter won't block
375         conf.setInt("split.outstanding", 5);
376         SplitAlgorithm splitAlgo = RegionSplitter.newSplitAlgoInstance(conf, splitClass);
377         RegionSplitter.rollingSplit(tableName, splitAlgo, conf);
378         verifyBounds(expectedBounds, tableName);
379     }
380 
381     private void verifyBounds(List<byte[]> expectedBounds, TableName tableName)
382             throws Exception {
383         // Get region boundaries from the cluster and verify their endpoints
384         final Configuration conf = UTIL.getConfiguration();
385         final int numRegions = expectedBounds.size()-1;
386         final HTable hTable = new HTable(conf, tableName);
387         final Map<HRegionInfo, ServerName> regionInfoMap = hTable.getRegionLocations();
388         assertEquals(numRegions, regionInfoMap.size());
389         for (Map.Entry<HRegionInfo, ServerName> entry: regionInfoMap.entrySet()) {
390             final HRegionInfo regionInfo = entry.getKey();
391             byte[] regionStart = regionInfo.getStartKey();
392             byte[] regionEnd = regionInfo.getEndKey();
393 
394             // This region's start key should be one of the region boundaries
395             int startBoundaryIndex = indexOfBytes(expectedBounds, regionStart);
396             assertNotSame(-1, startBoundaryIndex);
397 
398             // This region's end key should be the region boundary that comes
399             // after the starting boundary.
400             byte[] expectedRegionEnd = expectedBounds.get(
401                     startBoundaryIndex+1);
402             assertEquals(0, Bytes.compareTo(regionEnd, expectedRegionEnd));
403         }
404         hTable.close();
405     }
406 
407     /**
408      * List.indexOf() doesn't really work for a List<byte[]>, because byte[]
409      * doesn't override equals(). This method checks whether a list contains
410      * a given element by checking each element using the byte array
411      * comparator.
412      * @return the index of the first element that equals compareTo, or -1
413      * if no elements are equal.
414      */
415     static private int indexOfBytes(List<byte[]> list,  byte[] compareTo) {
416         int listIndex = 0;
417         for(byte[] elem: list) {
418             if(Bytes.BYTES_COMPARATOR.compare(elem, compareTo) == 0) {
419                 return listIndex;
420             }
421             listIndex++;
422         }
423         return -1;
424     }
425 
426 }
427