Index: src/java/org/apache/hadoop/hbase/client/HBaseAdmin.java =================================================================== --- src/java/org/apache/hadoop/hbase/client/HBaseAdmin.java (revision 939182) +++ src/java/org/apache/hadoop/hbase/client/HBaseAdmin.java (working copy) @@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.client; import java.io.IOException; +import java.util.Arrays; import java.util.Map; import java.util.NavigableMap; @@ -161,8 +162,79 @@ */ public void createTable(HTableDescriptor desc) throws IOException { + createTable(desc, null); + } + + /** + * Creates a new table with the specified number of regions. The start key + * specified will become the end key of the first region of the table, and + * the end key specified will become the start key of the last region of the + * table (the first region has a null start key and the last region has a + * null end key). + * + * BigInteger math will be used to divide the key range specified into + * enough segments to make the required number of total regions. + * + * Synchronous operation. + * + * @param desc table descriptor for table + * @param startKey beginning of key range + * @param endKey end of key range + * @param numRegions the total number of regions to create + * + * @throws IllegalArgumentException if the table name is reserved + * @throws MasterNotRunningException if master is not running + * @throws TableExistsException if table already exists (If concurrent + * threads, the table may have been created between test-for-existence + * and attempt-at-creation). + * @throws IOException + */ + public void createTable(HTableDescriptor desc, byte [] startKey, + byte [] endKey, int numRegions) + throws IOException { HTableDescriptor.isLegalTableName(desc.getName()); - createTableAsync(desc); + if(numRegions < 3) { + throw new IllegalArgumentException("Must create at least three regions"); + } else if(Bytes.compareTo(startKey, endKey) >= 0) { + throw new IllegalArgumentException("Start key must be smaller than end key"); + } + byte [][] splitKeys = Bytes.split(startKey, endKey, numRegions - 3); + if(splitKeys == null || splitKeys.length != numRegions - 1) { + throw new IllegalArgumentException("Unable to split key range into enough regions"); + } + createTable(desc, splitKeys); + } + + /** + * Creates a new table with an initial set of empty regions defined by the + * specified split keys. The total number of regions created will be the + * number of split keys plus one (the first region has a null start key and + * the last region has a null end key). + * Synchronous operation. + * + * @param desc table descriptor for table + * @param splitKeys array of split keys for the initial regions of the table + * + * @throws IllegalArgumentException if the table name is reserved + * @throws MasterNotRunningException if master is not running + * @throws TableExistsException if table already exists (If concurrent + * threads, the table may have been created between test-for-existence + * and attempt-at-creation). + * @throws IOException + */ + public void createTable(HTableDescriptor desc, byte [][] splitKeys) + throws IOException { + HTableDescriptor.isLegalTableName(desc.getName()); + Arrays.sort(splitKeys, Bytes.BYTES_COMPARATOR); + // Verify there are no duplicate split keys + byte [] lastKey = null; + for(byte [] splitKey : splitKeys) { + if(lastKey != null && Bytes.equals(splitKey, lastKey)) { + throw new IllegalArgumentException("All split keys must be unique, found duplicate"); + } + lastKey = splitKey; + } + createTableAsync(desc, splitKeys); for (int tries = 0; tries < numRetries; tries++) { try { // Wait for new table to come on-line @@ -196,14 +268,14 @@ * and attempt-at-creation). * @throws IOException */ - public void createTableAsync(HTableDescriptor desc) + public void createTableAsync(HTableDescriptor desc, byte [][] splitKeys) throws IOException { if (this.master == null) { throw new MasterNotRunningException("master has been shut down"); } HTableDescriptor.isLegalTableName(desc.getName()); try { - this.master.createTable(desc); + this.master.createTable(desc, splitKeys); } catch (RemoteException e) { throw RemoteExceptionHandler.decodeRemoteException(e); } Index: src/java/org/apache/hadoop/hbase/master/HMaster.java =================================================================== --- src/java/org/apache/hadoop/hbase/master/HMaster.java (revision 939182) +++ src/java/org/apache/hadoop/hbase/master/HMaster.java (working copy) @@ -741,13 +741,25 @@ this.zooKeeperWrapper.setClusterState(false); } - public void createTable(HTableDescriptor desc) + public void createTable(HTableDescriptor desc, byte [][] splitKeys) throws IOException { if (!isMasterRunning()) { throw new MasterNotRunningException(); } - HRegionInfo newRegion = new HRegionInfo(desc, null, null); - + HRegionInfo [] newRegions = null; + if(splitKeys == null || splitKeys.length == 0) { + newRegions = new HRegionInfo [] { new HRegionInfo(desc, null, null) }; + } else { + int numRegions = splitKeys.length + 1; + newRegions = new HRegionInfo[numRegions]; + byte [] startKey = null; + byte [] endKey = null; + for(int i=0;i 1) { - throw new IllegalArgumentException("b > a"); + if (compareTo(aPadded,bPadded) >= 0) { + throw new IllegalArgumentException("b <= a"); } if (num <= 0) throw new IllegalArgumentException("num cannot be < 0"); byte [] prependHeader = {1, 0}; @@ -1212,7 +1212,7 @@ BigInteger stopBI = new BigInteger(add(prependHeader, bPadded)); BigInteger diffBI = stopBI.subtract(startBI); BigInteger splitsBI = BigInteger.valueOf(num + 1); - if(diffBI.compareTo(splitsBI) <= 0) return null; + if(diffBI.compareTo(splitsBI) < 0) return null; BigInteger intervalBI = null; try { intervalBI = diffBI.divide(splitsBI); Index: src/test/org/apache/hadoop/hbase/client/TestHBaseAdmin.java =================================================================== --- src/test/org/apache/hadoop/hbase/client/TestHBaseAdmin.java (revision 939182) +++ src/test/org/apache/hadoop/hbase/client/TestHBaseAdmin.java (working copy) @@ -21,13 +21,16 @@ package org.apache.hadoop.hbase.client; import java.io.IOException; +import java.util.Iterator; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HBaseClusterTestCase; import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HServerAddress; import org.apache.hadoop.hbase.HTableDescriptor; -import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.util.Bytes; @@ -64,6 +67,163 @@ assertEquals(numTables + 1, tables.length); } + public void testCreateTableWithRegions() throws IOException { + init(); + + byte [][] splitKeys = { + new byte [] { 1, 1, 1 }, + new byte [] { 2, 2, 2 }, + new byte [] { 3, 3, 3 }, + new byte [] { 4, 4, 4 }, + new byte [] { 5, 5, 5 }, + new byte [] { 6, 6, 6 }, + new byte [] { 7, 7, 7 }, + new byte [] { 8, 8, 8 }, + new byte [] { 9, 9, 9 }, + }; + int expectedRegions = splitKeys.length + 1; + + HTableDescriptor desc = new HTableDescriptor(TABLE); + desc.addFamily(new HColumnDescriptor(FAMILY)); + admin = new HBaseAdmin(conf); + admin.createTable(desc, splitKeys); + + HTable ht = new HTable(conf, TABLE); + Map regions = ht.getRegionsInfo(); + assertEquals("Tried to create " + expectedRegions + " regions " + + "but only found " + regions.size(), + expectedRegions, regions.size()); + System.err.println("Found " + regions.size() + " regions"); + + Iterator hris = regions.keySet().iterator(); + HRegionInfo hri = hris.next(); + assertTrue(hri.getStartKey() == null || hri.getStartKey().length == 0); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[0])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[0])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[1])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[1])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[2])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[2])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[3])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[3])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[4])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[4])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[5])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[5])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[6])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[6])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[7])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[7])); + assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[8])); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[8])); + assertTrue(hri.getEndKey() == null || hri.getEndKey().length == 0); + + // Now test using start/end with a number of regions + + // Use 80 bit numbers to make sure we aren't limited + byte [] startKey = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + byte [] endKey = { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 }; + + // Splitting into 10 regions, we expect (null,1) ... (9, null) + // with (1,2) (2,3) (3,4) (4,5) (5,6) (6,7) (7,8) (8,9) in the middle + + expectedRegions = 10; + + byte [] TABLE_2 = Bytes.add(TABLE, Bytes.toBytes("_2")); + + desc = new HTableDescriptor(TABLE_2); + desc.addFamily(new HColumnDescriptor(FAMILY)); + admin = new HBaseAdmin(conf); + admin.createTable(desc, startKey, endKey, expectedRegions); + + ht = new HTable(conf, TABLE_2); + regions = ht.getRegionsInfo(); + assertEquals("Tried to create " + expectedRegions + " regions " + + "but only found " + regions.size(), + expectedRegions, regions.size()); + System.err.println("Found " + regions.size() + " regions"); + + hris = regions.keySet().iterator(); + hri = hris.next(); + assertTrue(hri.getStartKey() == null || hri.getStartKey().length == 0); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {1,1,1,1,1,1,1,1,1,1})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {1,1,1,1,1,1,1,1,1,1})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {2,2,2,2,2,2,2,2,2,2})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {2,2,2,2,2,2,2,2,2,2})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {3,3,3,3,3,3,3,3,3,3})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {3,3,3,3,3,3,3,3,3,3})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {4,4,4,4,4,4,4,4,4,4})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {4,4,4,4,4,4,4,4,4,4})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {5,5,5,5,5,5,5,5,5,5})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {5,5,5,5,5,5,5,5,5,5})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {6,6,6,6,6,6,6,6,6,6})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {6,6,6,6,6,6,6,6,6,6})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {7,7,7,7,7,7,7,7,7,7})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {7,7,7,7,7,7,7,7,7,7})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {8,8,8,8,8,8,8,8,8,8})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {8,8,8,8,8,8,8,8,8,8})); + assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {9,9,9,9,9,9,9,9,9,9})); + hri = hris.next(); + assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {9,9,9,9,9,9,9,9,9,9})); + assertTrue(hri.getEndKey() == null || hri.getEndKey().length == 0); + + // Try once more with something that divides into something infinite + + startKey = new byte [] { 0, 0, 0, 0, 0, 0 }; + endKey = new byte [] { 1, 0, 0, 0, 0, 0 }; + + expectedRegions = 5; + + byte [] TABLE_3 = Bytes.add(TABLE, Bytes.toBytes("_3")); + + desc = new HTableDescriptor(TABLE_3); + desc.addFamily(new HColumnDescriptor(FAMILY)); + admin = new HBaseAdmin(conf); + admin.createTable(desc, startKey, endKey, expectedRegions); + + ht = new HTable(conf, TABLE_3); + regions = ht.getRegionsInfo(); + assertEquals("Tried to create " + expectedRegions + " regions " + + "but only found " + regions.size(), + expectedRegions, regions.size()); + System.err.println("Found " + regions.size() + " regions"); + + // Try an invalid case where there are duplicate split keys + splitKeys = new byte [][] { + new byte [] { 1, 1, 1 }, + new byte [] { 2, 2, 2 }, + new byte [] { 3, 3, 3 }, + new byte [] { 2, 2, 2 } + }; + + byte [] TABLE_4 = Bytes.add(TABLE, Bytes.toBytes("_4")); + desc = new HTableDescriptor(TABLE_4); + desc.addFamily(new HColumnDescriptor(FAMILY)); + admin = new HBaseAdmin(conf); + try { + admin.createTable(desc, splitKeys); + assertTrue("Should not be able to create this table because of duplicate split keys", false); + } catch(IllegalArgumentException iae) { + // Expected + } + } public void testDisableAndEnableTable() throws IOException { init(); Index: src/test/org/apache/hadoop/hbase/util/TestBytes.java =================================================================== --- src/test/org/apache/hadoop/hbase/util/TestBytes.java (revision 939182) +++ src/test/org/apache/hadoop/hbase/util/TestBytes.java (working copy) @@ -20,11 +20,10 @@ package org.apache.hadoop.hbase.util; import java.io.IOException; -import java.util.Arrays; import java.math.BigDecimal; +import java.util.Arrays; import junit.framework.TestCase; -import junit.framework.Assert; public class TestBytes extends TestCase { public void testNullHashCode() { @@ -71,6 +70,31 @@ assertEquals(3, parts.length); assertTrue(Bytes.equals(parts[1], middle)); } + + public void testSplit3() throws Exception { + // Test invalid split cases + byte [] low = { 1, 1, 1 }; + byte [] high = { 1, 1, 3 }; + + // If swapped, should throw IAE + try { + Bytes.split(high, low, 1); + assertTrue("Should not be able to split if low > high", false); + } catch(IllegalArgumentException iae) { + // Correct + } + + // Single split should work + byte [][] parts = Bytes.split(low, high, 1); + for (int i = 0; i < parts.length; i++) { + System.out.println("" + i + " -> " + Bytes.toStringBinary(parts[i])); + } + assertTrue("Returned split should have 3 parts but has " + parts.length, parts.length == 3); + + // If split more than once, this should fail + parts = Bytes.split(low, high, 2); + assertTrue("Returned split but should have failed", parts == null); + } public void testToChars() throws Exception { char[] chars = new char[]{'b', 'l', 'a', 'b', 'l', 'a', 'b', 'l', 'a'};