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