1   /**
2    * Copyright 2009 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.client;
21  
22  
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertFalse;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  import java.util.concurrent.atomic.AtomicInteger;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.hadoop.hbase.HBaseTestingUtility;
39  import org.apache.hadoop.hbase.HColumnDescriptor;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.HRegionInfo;
42  import org.apache.hadoop.hbase.HServerAddress;
43  import org.apache.hadoop.hbase.HTableDescriptor;
44  import org.apache.hadoop.hbase.NotServingRegionException;
45  import org.apache.hadoop.hbase.TableExistsException;
46  import org.apache.hadoop.hbase.TableNotDisabledException;
47  import org.apache.hadoop.hbase.TableNotFoundException;
48  import org.apache.hadoop.hbase.executor.EventHandler;
49  import org.apache.hadoop.hbase.executor.ExecutorService;
50  import org.apache.hadoop.hbase.executor.EventHandler.EventType;
51  import org.apache.hadoop.hbase.master.AssignmentManager;
52  import org.apache.hadoop.hbase.master.MasterServices;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.junit.AfterClass;
55  import org.junit.Before;
56  import org.junit.BeforeClass;
57  import org.junit.Test;
58  
59  
60  /**
61   * Class to test HBaseAdmin.
62   * Spins up the minicluster once at test start and then takes it down afterward.
63   * Add any testing of HBaseAdmin functionality here.
64   */
65  public class TestAdmin {
66    final Log LOG = LogFactory.getLog(getClass());
67    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
68    private HBaseAdmin admin;
69  
70    @BeforeClass
71    public static void setUpBeforeClass() throws Exception {
72      TEST_UTIL.getConfiguration().setInt("hbase.regionserver.msginterval", 100);
73      TEST_UTIL.getConfiguration().setInt("hbase.client.pause", 250);
74      TEST_UTIL.getConfiguration().setInt("hbase.client.retries.number", 6);
75      TEST_UTIL.startMiniCluster(3);
76    }
77  
78    @AfterClass
79    public static void tearDownAfterClass() throws Exception {
80      TEST_UTIL.shutdownMiniCluster();
81    }
82  
83    @Before
84    public void setUp() throws Exception {
85      this.admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
86    }
87  
88    @Test
89    public void testDisableAndEnableTable() throws IOException {
90      final byte [] row = Bytes.toBytes("row");
91      final byte [] qualifier = Bytes.toBytes("qualifier");
92      final byte [] value = Bytes.toBytes("value");
93      final byte [] table = Bytes.toBytes("testDisableAndEnableTable");
94      HTable ht = TEST_UTIL.createTable(table, HConstants.CATALOG_FAMILY);
95      Put put = new Put(row);
96      put.add(HConstants.CATALOG_FAMILY, qualifier, value);
97      ht.put(put);
98      Get get = new Get(row);
99      get.addColumn(HConstants.CATALOG_FAMILY, qualifier);
100     ht.get(get);
101 
102     this.admin.disableTable(table);
103 
104     // Test that table is disabled
105     get = new Get(row);
106     get.addColumn(HConstants.CATALOG_FAMILY, qualifier);
107     boolean ok = false;
108     try {
109       ht.get(get);
110     } catch (NotServingRegionException e) {
111       ok = true;
112     } catch (RetriesExhaustedException e) {
113       ok = true;
114     }
115     assertTrue(ok);
116     this.admin.enableTable(table);
117 
118     // Test that table is enabled
119     try {
120       ht.get(get);
121     } catch (RetriesExhaustedException e) {
122       ok = false;
123     }
124     assertTrue(ok);
125   }
126 
127   @Test
128   public void testCreateTable() throws IOException {
129     HTableDescriptor [] tables = admin.listTables();
130     int numTables = tables.length;
131     TEST_UTIL.createTable(Bytes.toBytes("testCreateTable"),
132       HConstants.CATALOG_FAMILY);
133     tables = this.admin.listTables();
134     assertEquals(numTables + 1, tables.length);
135   }
136 
137   @Test
138   public void testGetTableDescriptor() throws IOException {
139     HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
140     HColumnDescriptor fam2 = new HColumnDescriptor("fam2");
141     HColumnDescriptor fam3 = new HColumnDescriptor("fam3");
142     HTableDescriptor htd = new HTableDescriptor("myTestTable");
143     htd.addFamily(fam1);
144     htd.addFamily(fam2);
145     htd.addFamily(fam3);
146     this.admin.createTable(htd);
147     HTable table = new HTable(TEST_UTIL.getConfiguration(), "myTestTable");
148     HTableDescriptor confirmedHtd = table.getTableDescriptor();
149     assertEquals(htd.compareTo(confirmedHtd), 0);
150   }
151 
152   /**
153    * Verify schema modification takes.
154    * @throws IOException
155    */
156   @Test
157   public void testChangeTableSchema() throws IOException {
158     final byte [] tableName = Bytes.toBytes("changeTableSchema");
159     HTableDescriptor [] tables = admin.listTables();
160     int numTables = tables.length;
161     TEST_UTIL.createTable(tableName, HConstants.CATALOG_FAMILY);
162     tables = this.admin.listTables();
163     assertEquals(numTables + 1, tables.length);
164 
165     // FIRST, do htabledescriptor changes.
166     HTableDescriptor htd = this.admin.getTableDescriptor(tableName);
167     // Make a copy and assert copy is good.
168     HTableDescriptor copy = new HTableDescriptor(htd);
169     assertTrue(htd.equals(copy));
170     // Now amend the copy. Introduce differences.
171     long newFlushSize = htd.getMemStoreFlushSize() / 2;
172     copy.setMemStoreFlushSize(newFlushSize);
173     final String key = "anyoldkey";
174     assertTrue(htd.getValue(key) == null);
175     copy.setValue(key, key);
176     boolean expectedException = false;
177     try {
178       this.admin.modifyTable(tableName, copy);
179     } catch (TableNotDisabledException re) {
180       expectedException = true;
181     }
182     assertTrue(expectedException);
183     this.admin.disableTable(tableName);
184     assertTrue(this.admin.isTableDisabled(tableName));
185     modifyTable(tableName, copy);
186     HTableDescriptor modifiedHtd = this.admin.getTableDescriptor(tableName);
187     // Assert returned modifiedhcd is same as the copy.
188     assertFalse(htd.equals(modifiedHtd));
189     assertTrue(copy.equals(modifiedHtd));
190     assertEquals(newFlushSize, modifiedHtd.getMemStoreFlushSize());
191     assertEquals(key, modifiedHtd.getValue(key));
192 
193     // Reenable table to test it fails if not disabled.
194     this.admin.enableTable(tableName);
195     assertFalse(this.admin.isTableDisabled(tableName));
196 
197     // Now work on column family changes.
198     int countOfFamilies = modifiedHtd.getFamilies().size();
199     assertTrue(countOfFamilies > 0);
200     HColumnDescriptor hcd = modifiedHtd.getFamilies().iterator().next();
201     int maxversions = hcd.getMaxVersions();
202     final int newMaxVersions = maxversions + 1;
203     hcd.setMaxVersions(newMaxVersions);
204     final byte [] hcdName = hcd.getName();
205     expectedException = false;
206     try {
207       this.admin.modifyColumn(tableName, hcd);
208     } catch (TableNotDisabledException re) {
209       expectedException = true;
210     }
211     assertTrue(expectedException);
212     this.admin.disableTable(tableName);
213     assertTrue(this.admin.isTableDisabled(tableName));
214     // Modify Column is synchronous
215     this.admin.modifyColumn(tableName, hcd);
216     modifiedHtd = this.admin.getTableDescriptor(tableName);
217     HColumnDescriptor modifiedHcd = modifiedHtd.getFamily(hcdName);
218     assertEquals(newMaxVersions, modifiedHcd.getMaxVersions());
219 
220     // Try adding a column
221     // Reenable table to test it fails if not disabled.
222     this.admin.enableTable(tableName);
223     assertFalse(this.admin.isTableDisabled(tableName));
224     final String xtracolName = "xtracol";
225     HColumnDescriptor xtracol = new HColumnDescriptor(xtracolName);
226     xtracol.setValue(xtracolName, xtracolName);
227     try {
228       this.admin.addColumn(tableName, xtracol);
229     } catch (TableNotDisabledException re) {
230       expectedException = true;
231     }
232     assertTrue(expectedException);
233     this.admin.disableTable(tableName);
234     assertTrue(this.admin.isTableDisabled(tableName));
235     this.admin.addColumn(tableName, xtracol);
236     modifiedHtd = this.admin.getTableDescriptor(tableName);
237     hcd = modifiedHtd.getFamily(xtracol.getName());
238     assertTrue(hcd != null);
239     assertTrue(hcd.getValue(xtracolName).equals(xtracolName));
240 
241     // Delete the just-added column.
242     this.admin.deleteColumn(tableName, xtracol.getName());
243     modifiedHtd = this.admin.getTableDescriptor(tableName);
244     hcd = modifiedHtd.getFamily(xtracol.getName());
245     assertTrue(hcd == null);
246 
247     // Delete the table
248     this.admin.deleteTable(tableName);
249     this.admin.listTables();
250     assertFalse(this.admin.tableExists(tableName));
251   }
252 
253   /**
254    * Modify table is async so wait on completion of the table operation in master.
255    * @param tableName
256    * @param htd
257    * @throws IOException
258    */
259   private void modifyTable(final byte [] tableName, final HTableDescriptor htd)
260   throws IOException {
261     MasterServices services = TEST_UTIL.getMiniHBaseCluster().getMaster();
262     ExecutorService executor = services.getExecutorService();
263     AtomicBoolean done = new AtomicBoolean(false);
264     executor.registerListener(EventType.C_M_MODIFY_TABLE, new DoneListener(done));
265     this.admin.modifyTable(tableName, htd);
266     while (!done.get()) {
267       synchronized (done) {
268         try {
269           done.wait(1000);
270         } catch (InterruptedException e) {
271           e.printStackTrace();
272         }
273       }
274     }
275     executor.unregisterListener(EventType.C_M_MODIFY_TABLE);
276   }
277 
278   /**
279    * Listens for when an event is done in Master.
280    */
281   static class DoneListener implements EventHandler.EventHandlerListener {
282     private final AtomicBoolean done;
283 
284     DoneListener(final AtomicBoolean done) {
285       super();
286       this.done = done;
287     }
288 
289     @Override
290     public void afterProcess(EventHandler event) {
291       this.done.set(true);
292       synchronized (this.done) {
293         // Wake anyone waiting on this value to change.
294         this.done.notifyAll();
295       }
296     }
297 
298     @Override
299     public void beforeProcess(EventHandler event) {
300       // continue
301     }
302   }
303 
304   protected void verifyRoundRobinDistribution(HTable ht, int expectedRegions) throws IOException {
305     MasterServices services = TEST_UTIL.getMiniHBaseCluster().getMaster();
306     AssignmentManager am = services.getAssignmentManager();
307     Map<HRegionInfo,HServerAddress> regions = ht.getRegionsInfo();
308     for (HRegionInfo regionInfo : regions.keySet()) {
309       try {
310         am.waitForAssignment(regionInfo);
311       } catch (InterruptedException e) {
312         LOG.info("Interrupted waiting for region to be assigned during " +
313             "create table call", e);
314         Thread.currentThread().interrupt();
315         return;
316       }
317     }    
318     int numRS = ht.getCurrentNrHRS();
319     regions = ht.getRegionsInfo();
320     Map<HServerAddress, List<HRegionInfo>> server2Regions = new HashMap<HServerAddress, List<HRegionInfo>>();
321     for (Map.Entry<HRegionInfo,HServerAddress> entry : regions.entrySet()) {
322       HServerAddress server = entry.getValue();
323       List<HRegionInfo> regs = server2Regions.get(server);
324       if (regs == null) {
325         regs = new ArrayList<HRegionInfo>();
326         server2Regions.put(server, regs);
327       }
328       regs.add(entry.getKey());
329     }
330     float average = (float) expectedRegions/numRS;
331     int min = (int)Math.floor(average);
332     int max = (int)Math.ceil(average);
333     for (List<HRegionInfo> regionList : server2Regions.values()) {
334       assertTrue(regionList.size() == min || regionList.size() == max);
335     }
336   }
337 
338   @Test
339   public void testCreateTableWithRegions() throws IOException, InterruptedException {
340 
341     byte[] tableName = Bytes.toBytes("testCreateTableWithRegions");
342 
343     byte [][] splitKeys = {
344         new byte [] { 1, 1, 1 },
345         new byte [] { 2, 2, 2 },
346         new byte [] { 3, 3, 3 },
347         new byte [] { 4, 4, 4 },
348         new byte [] { 5, 5, 5 },
349         new byte [] { 6, 6, 6 },
350         new byte [] { 7, 7, 7 },
351         new byte [] { 8, 8, 8 },
352         new byte [] { 9, 9, 9 },
353     };
354     int expectedRegions = splitKeys.length + 1;
355 
356     HTableDescriptor desc = new HTableDescriptor(tableName);
357     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
358     admin.createTable(desc, splitKeys);
359 
360     HTable ht = new HTable(TEST_UTIL.getConfiguration(), tableName);
361     Map<HRegionInfo,HServerAddress> regions = ht.getRegionsInfo();
362     assertEquals("Tried to create " + expectedRegions + " regions " +
363         "but only found " + regions.size(),
364         expectedRegions, regions.size());
365     System.err.println("Found " + regions.size() + " regions");
366 
367     Iterator<HRegionInfo> hris = regions.keySet().iterator();
368     HRegionInfo hri = hris.next();
369     assertTrue(hri.getStartKey() == null || hri.getStartKey().length == 0);
370     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[0]));
371     hri = hris.next();
372     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[0]));
373     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[1]));
374     hri = hris.next();
375     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[1]));
376     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[2]));
377     hri = hris.next();
378     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[2]));
379     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[3]));
380     hri = hris.next();
381     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[3]));
382     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[4]));
383     hri = hris.next();
384     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[4]));
385     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[5]));
386     hri = hris.next();
387     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[5]));
388     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[6]));
389     hri = hris.next();
390     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[6]));
391     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[7]));
392     hri = hris.next();
393     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[7]));
394     assertTrue(Bytes.equals(hri.getEndKey(), splitKeys[8]));
395     hri = hris.next();
396     assertTrue(Bytes.equals(hri.getStartKey(), splitKeys[8]));
397     assertTrue(hri.getEndKey() == null || hri.getEndKey().length == 0);
398 
399     verifyRoundRobinDistribution(ht, expectedRegions);
400 
401     // Now test using start/end with a number of regions
402 
403     // Use 80 bit numbers to make sure we aren't limited
404     byte [] startKey = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
405     byte [] endKey =   { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 };
406 
407     // Splitting into 10 regions, we expect (null,1) ... (9, null)
408     // with (1,2) (2,3) (3,4) (4,5) (5,6) (6,7) (7,8) (8,9) in the middle
409 
410     expectedRegions = 10;
411 
412     byte [] TABLE_2 = Bytes.add(tableName, Bytes.toBytes("_2"));
413 
414     desc = new HTableDescriptor(TABLE_2);
415     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
416     admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
417     admin.createTable(desc, startKey, endKey, expectedRegions);
418 
419     ht = new HTable(TEST_UTIL.getConfiguration(), TABLE_2);
420     regions = ht.getRegionsInfo();
421     assertEquals("Tried to create " + expectedRegions + " regions " +
422         "but only found " + regions.size(),
423         expectedRegions, regions.size());
424     System.err.println("Found " + regions.size() + " regions");
425 
426     hris = regions.keySet().iterator();
427     hri = hris.next();
428     assertTrue(hri.getStartKey() == null || hri.getStartKey().length == 0);
429     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {1,1,1,1,1,1,1,1,1,1}));
430     hri = hris.next();
431     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {1,1,1,1,1,1,1,1,1,1}));
432     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {2,2,2,2,2,2,2,2,2,2}));
433     hri = hris.next();
434     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {2,2,2,2,2,2,2,2,2,2}));
435     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {3,3,3,3,3,3,3,3,3,3}));
436     hri = hris.next();
437     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {3,3,3,3,3,3,3,3,3,3}));
438     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {4,4,4,4,4,4,4,4,4,4}));
439     hri = hris.next();
440     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {4,4,4,4,4,4,4,4,4,4}));
441     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {5,5,5,5,5,5,5,5,5,5}));
442     hri = hris.next();
443     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {5,5,5,5,5,5,5,5,5,5}));
444     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {6,6,6,6,6,6,6,6,6,6}));
445     hri = hris.next();
446     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {6,6,6,6,6,6,6,6,6,6}));
447     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {7,7,7,7,7,7,7,7,7,7}));
448     hri = hris.next();
449     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {7,7,7,7,7,7,7,7,7,7}));
450     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {8,8,8,8,8,8,8,8,8,8}));
451     hri = hris.next();
452     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {8,8,8,8,8,8,8,8,8,8}));
453     assertTrue(Bytes.equals(hri.getEndKey(), new byte [] {9,9,9,9,9,9,9,9,9,9}));
454     hri = hris.next();
455     assertTrue(Bytes.equals(hri.getStartKey(), new byte [] {9,9,9,9,9,9,9,9,9,9}));
456     assertTrue(hri.getEndKey() == null || hri.getEndKey().length == 0);
457 
458     verifyRoundRobinDistribution(ht, expectedRegions);
459 
460     // Try once more with something that divides into something infinite
461 
462     startKey = new byte [] { 0, 0, 0, 0, 0, 0 };
463     endKey = new byte [] { 1, 0, 0, 0, 0, 0 };
464 
465     expectedRegions = 5;
466 
467     byte [] TABLE_3 = Bytes.add(tableName, Bytes.toBytes("_3"));
468 
469     desc = new HTableDescriptor(TABLE_3);
470     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
471     admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
472     admin.createTable(desc, startKey, endKey, expectedRegions);
473 
474     ht = new HTable(TEST_UTIL.getConfiguration(), TABLE_3);
475     regions = ht.getRegionsInfo();
476     assertEquals("Tried to create " + expectedRegions + " regions " +
477         "but only found " + regions.size(),
478         expectedRegions, regions.size());
479     System.err.println("Found " + regions.size() + " regions");
480 
481     verifyRoundRobinDistribution(ht, expectedRegions);
482 
483     // Try an invalid case where there are duplicate split keys
484     splitKeys = new byte [][] {
485         new byte [] { 1, 1, 1 },
486         new byte [] { 2, 2, 2 },
487         new byte [] { 3, 3, 3 },
488         new byte [] { 2, 2, 2 }
489     };
490 
491     byte [] TABLE_4 = Bytes.add(tableName, Bytes.toBytes("_4"));
492     desc = new HTableDescriptor(TABLE_4);
493     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
494     admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
495     try {
496       admin.createTable(desc, splitKeys);
497       assertTrue("Should not be able to create this table because of " +
498           "duplicate split keys", false);
499     } catch(IllegalArgumentException iae) {
500       // Expected
501     }
502   }
503 
504   @Test
505   public void testTableExist() throws IOException {
506     final byte [] table = Bytes.toBytes("testTableExist");
507     boolean exist = false;
508     exist = this.admin.tableExists(table);
509     assertEquals(false, exist);
510     TEST_UTIL.createTable(table, HConstants.CATALOG_FAMILY);
511     exist = this.admin.tableExists(table);
512     assertEquals(true, exist);
513   }
514 
515   /**
516    * Tests forcing split from client and having scanners successfully ride over split.
517    * @throws Exception
518    * @throws IOException
519    */
520   @Test
521   public void testForceSplit() throws Exception {
522       splitTest(null);
523       splitTest(Bytes.toBytes("pwn"));
524     }
525 
526   void splitTest(byte[] splitPoint) throws Exception {
527     byte [] familyName = HConstants.CATALOG_FAMILY;
528     byte [] tableName = Bytes.toBytes("testForceSplit");
529     assertFalse(admin.tableExists(tableName));
530     final HTable table = TEST_UTIL.createTable(tableName, familyName);
531     try {
532       byte[] k = new byte[3];
533       int rowCount = 0;
534       for (byte b1 = 'a'; b1 < 'z'; b1++) {
535         for (byte b2 = 'a'; b2 < 'z'; b2++) {
536           for (byte b3 = 'a'; b3 < 'z'; b3++) {
537             k[0] = b1;
538             k[1] = b2;
539             k[2] = b3;
540             Put put = new Put(k);
541             put.add(familyName, new byte[0], k);
542             table.put(put);
543             rowCount++;
544           }
545         }
546       }
547 
548       // get the initial layout (should just be one region)
549       Map<HRegionInfo,HServerAddress> m = table.getRegionsInfo();
550       System.out.println("Initial regions (" + m.size() + "): " + m);
551       assertTrue(m.size() == 1);
552 
553       // Verify row count
554       Scan scan = new Scan();
555       ResultScanner scanner = table.getScanner(scan);
556       int rows = 0;
557       for(@SuppressWarnings("unused") Result result : scanner) {
558         rows++;
559       }
560       scanner.close();
561       assertEquals(rowCount, rows);
562 
563       // Have an outstanding scan going on to make sure we can scan over splits.
564       scan = new Scan();
565       scanner = table.getScanner(scan);
566       // Scan first row so we are into first region before split happens.
567       scanner.next();
568 
569       final AtomicInteger count = new AtomicInteger(0);
570       Thread t = new Thread("CheckForSplit") {
571         public void run() {
572           for (int i = 0; i < 20; i++) {
573             try {
574               sleep(1000);
575             } catch (InterruptedException e) {
576               continue;
577             }
578             // check again    table = new HTable(conf, tableName);
579             Map<HRegionInfo, HServerAddress> regions = null;
580             try {
581               regions = table.getRegionsInfo();
582             } catch (IOException e) {
583               e.printStackTrace();
584             }
585             if (regions == null) continue;
586             count.set(regions.size());
587             if (count.get() >= 2) break;
588             LOG.debug("Cycle waiting on split");
589           }
590         }
591       };
592       t.start();
593       // Split the table
594       this.admin.split(tableName, splitPoint);
595       t.join();
596 
597       // Verify row count
598       rows = 1; // We counted one row above.
599       for (@SuppressWarnings("unused") Result result : scanner) {
600         rows++;
601         if (rows > rowCount) {
602           scanner.close();
603           assertTrue("Scanned more than expected (" + rowCount + ")", false);
604         }
605       }
606       scanner.close();
607       assertEquals(rowCount, rows);
608 
609       if (splitPoint != null) {
610         // make sure the split point matches our explicit configuration
611         Map<HRegionInfo, HServerAddress> regions = null;
612         try {
613           regions = table.getRegionsInfo();
614         } catch (IOException e) {
615           e.printStackTrace();
616         }
617         assertEquals(2, regions.size());
618         HRegionInfo[] r = regions.keySet().toArray(new HRegionInfo[0]);
619         assertEquals(Bytes.toString(splitPoint),
620             Bytes.toString(r[0].getEndKey()));
621         assertEquals(Bytes.toString(splitPoint),
622             Bytes.toString(r[1].getStartKey()));
623         LOG.debug("Properly split on " + Bytes.toString(splitPoint));
624       }
625     } finally {
626       TEST_UTIL.deleteTable(tableName);
627     }
628   }
629 
630   /**
631    * HADOOP-2156
632    * @throws IOException
633    */
634   @Test (expected=IllegalArgumentException.class)
635   public void testEmptyHHTableDescriptor() throws IOException {
636     this.admin.createTable(new HTableDescriptor());
637   }
638 
639   @Test
640   public void testEnableDisableAddColumnDeleteColumn() throws Exception {
641     byte [] tableName = Bytes.toBytes("testMasterAdmin");
642     TEST_UTIL.createTable(tableName, HConstants.CATALOG_FAMILY);
643     this.admin.disableTable(tableName);
644     try {
645       new HTable(TEST_UTIL.getConfiguration(), tableName);
646     } catch (org.apache.hadoop.hbase.client.RegionOfflineException e) {
647       // Expected
648     }
649     this.admin.addColumn(tableName, new HColumnDescriptor("col2"));
650     this.admin.enableTable(tableName);
651     try {
652       this.admin.deleteColumn(tableName, Bytes.toBytes("col2"));
653     } catch(TableNotDisabledException e) {
654       // Expected
655     }
656     this.admin.disableTable(tableName);
657     this.admin.deleteColumn(tableName, Bytes.toBytes("col2"));
658     this.admin.deleteTable(tableName);
659   }
660 
661   @Test
662   public void testCreateBadTables() throws IOException {
663     String msg = null;
664     try {
665       this.admin.createTable(HTableDescriptor.ROOT_TABLEDESC);
666     } catch (IllegalArgumentException e) {
667       msg = e.toString();
668     }
669     assertTrue("Unexcepted exception message " + msg, msg != null &&
670       msg.startsWith(IllegalArgumentException.class.getName()) &&
671       msg.contains(HTableDescriptor.ROOT_TABLEDESC.getNameAsString()));
672     msg = null;
673     try {
674       this.admin.createTable(HTableDescriptor.META_TABLEDESC);
675     } catch(IllegalArgumentException e) {
676       msg = e.toString();
677     }
678     assertTrue("Unexcepted exception message " + msg, msg != null &&
679       msg.startsWith(IllegalArgumentException.class.getName()) &&
680       msg.contains(HTableDescriptor.META_TABLEDESC.getNameAsString()));
681 
682     // Now try and do concurrent creation with a bunch of threads.
683     final HTableDescriptor threadDesc =
684       new HTableDescriptor("threaded_testCreateBadTables");
685     threadDesc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
686     int count = 10;
687     Thread [] threads = new Thread [count];
688     final AtomicInteger successes = new AtomicInteger(0);
689     final AtomicInteger failures = new AtomicInteger(0);
690     final HBaseAdmin localAdmin = this.admin;
691     for (int i = 0; i < count; i++) {
692       threads[i] = new Thread(Integer.toString(i)) {
693         @Override
694         public void run() {
695           try {
696             localAdmin.createTable(threadDesc);
697             successes.incrementAndGet();
698           } catch (TableExistsException e) {
699             failures.incrementAndGet();
700           } catch (IOException e) {
701             throw new RuntimeException("Failed threaded create" + getName(), e);
702           }
703         }
704       };
705     }
706     for (int i = 0; i < count; i++) {
707       threads[i].start();
708     }
709     for (int i = 0; i < count; i++) {
710       while(threads[i].isAlive()) {
711         try {
712           Thread.sleep(1000);
713         } catch (InterruptedException e) {
714           // continue
715         }
716       }
717     }
718     // All threads are now dead.  Count up how many tables were created and
719     // how many failed w/ appropriate exception.
720     assertEquals(1, successes.get());
721     assertEquals(count - 1, failures.get());
722   }
723 
724   /**
725    * Test for hadoop-1581 'HBASE: Unopenable tablename bug'.
726    * @throws Exception
727    */
728   @Test
729   public void testTableNameClash() throws Exception {
730     String name = "testTableNameClash";
731     admin.createTable(new HTableDescriptor(name + "SOMEUPPERCASE"));
732     admin.createTable(new HTableDescriptor(name));
733     // Before fix, below would fail throwing a NoServerForRegionException.
734     new HTable(TEST_UTIL.getConfiguration(), name);
735   }
736 
737   /**
738    * Test read only tables
739    * @throws Exception
740    */
741   @Test
742   public void testReadOnlyTable() throws Exception {
743     byte [] name = Bytes.toBytes("testReadOnlyTable");
744     HTable table = TEST_UTIL.createTable(name, HConstants.CATALOG_FAMILY);
745     byte[] value = Bytes.toBytes("somedata");
746     // This used to use an empty row... That must have been a bug
747     Put put = new Put(value);
748     put.add(HConstants.CATALOG_FAMILY, HConstants.CATALOG_FAMILY, value);
749     table.put(put);
750   }
751 
752   /**
753    * Test that user table names can contain '-' and '.' so long as they do not
754    * start with same. HBASE-771
755    * @throws IOException
756    */
757   @Test
758   public void testTableNames() throws IOException {
759     byte[][] illegalNames = new byte[][] {
760         Bytes.toBytes("-bad"),
761         Bytes.toBytes(".bad"),
762         HConstants.ROOT_TABLE_NAME,
763         HConstants.META_TABLE_NAME
764     };
765     for (int i = 0; i < illegalNames.length; i++) {
766       try {
767         new HTableDescriptor(illegalNames[i]);
768         throw new IOException("Did not detect '" +
769           Bytes.toString(illegalNames[i]) + "' as an illegal user table name");
770       } catch (IllegalArgumentException e) {
771         // expected
772       }
773     }
774     byte[] legalName = Bytes.toBytes("g-oo.d");
775     try {
776       new HTableDescriptor(legalName);
777     } catch (IllegalArgumentException e) {
778       throw new IOException("Legal user table name: '" +
779         Bytes.toString(legalName) + "' caused IllegalArgumentException: " +
780         e.getMessage());
781     }
782   }
783 
784   /**
785    * For HADOOP-2579
786    * @throws IOException
787    */
788   @Test (expected=TableExistsException.class)
789   public void testTableNotFoundExceptionWithATable() throws IOException {
790     final byte [] name = Bytes.toBytes("testTableNotFoundExceptionWithATable");
791     TEST_UTIL.createTable(name, HConstants.CATALOG_FAMILY);
792     TEST_UTIL.createTable(name, HConstants.CATALOG_FAMILY);
793   }
794 
795   /**
796    * For HADOOP-2579
797    * @throws IOException
798    */
799   @Test (expected=TableNotFoundException.class)
800   public void testTableNotFoundExceptionWithoutAnyTables() throws IOException {
801     new HTable(TEST_UTIL.getConfiguration(),
802         "testTableNotFoundExceptionWithoutAnyTables");
803   }
804 
805   @Test
806   public void testHundredsOfTable() throws IOException{
807     final int times = 100;
808     HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
809     HColumnDescriptor fam2 = new HColumnDescriptor("fam2");
810     HColumnDescriptor fam3 = new HColumnDescriptor("fam3");
811 
812     for(int i = 0; i < times; i++) {
813       HTableDescriptor htd = new HTableDescriptor("table"+i);
814       htd.addFamily(fam1);
815       htd.addFamily(fam2);
816       htd.addFamily(fam3);
817       this.admin.createTable(htd);
818     }
819 
820     for(int i = 0; i < times; i++) {
821       String tableName = "table"+i;
822       this.admin.disableTable(tableName);
823       byte [] tableNameBytes = Bytes.toBytes(tableName);
824       assertTrue(this.admin.isTableDisabled(tableNameBytes));
825       this.admin.enableTable(tableName);
826       assertFalse(this.admin.isTableDisabled(tableNameBytes));
827       this.admin.disableTable(tableName);
828       assertTrue(this.admin.isTableDisabled(tableNameBytes));
829       this.admin.deleteTable(tableName);
830     }
831   }
832 }