View Javadoc

1   /**
2    * Copyright 2010 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  import java.io.IOException;
23  import java.io.InterruptedIOException;
24  import java.net.SocketTimeoutException;
25  import java.util.Arrays;
26  import java.util.List;
27  import java.util.concurrent.atomic.AtomicInteger;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.Abortable;
33  import org.apache.hadoop.hbase.ClusterStatus;
34  import org.apache.hadoop.hbase.HBaseConfiguration;
35  import org.apache.hadoop.hbase.HColumnDescriptor;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.HRegionLocation;
39  import org.apache.hadoop.hbase.HServerAddress;
40  import org.apache.hadoop.hbase.HTableDescriptor;
41  import org.apache.hadoop.hbase.MasterNotRunningException;
42  import org.apache.hadoop.hbase.NotServingRegionException;
43  import org.apache.hadoop.hbase.RegionException;
44  import org.apache.hadoop.hbase.RemoteExceptionHandler;
45  import org.apache.hadoop.hbase.TableExistsException;
46  import org.apache.hadoop.hbase.UnknownRegionException;
47  import org.apache.hadoop.hbase.ZooKeeperConnectionException;
48  import org.apache.hadoop.hbase.catalog.CatalogTracker;
49  import org.apache.hadoop.hbase.catalog.MetaReader;
50  import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
51  import org.apache.hadoop.hbase.ipc.HMasterInterface;
52  import org.apache.hadoop.hbase.ipc.HRegionInterface;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.apache.hadoop.hbase.util.Pair;
55  import org.apache.hadoop.hbase.util.Writables;
56  import org.apache.hadoop.ipc.RemoteException;
57  import org.apache.hadoop.util.StringUtils;
58  
59  /**
60   * Provides an interface to manage HBase database table metadata + general 
61   * administrative functions.  Use HBaseAdmin to create, drop, list, enable and 
62   * disable tables. Use it also to add and drop table column families. 
63   * 
64   * <p>See {@link HTable} to add, update, and delete data from an individual table.
65   * <p>Currently HBaseAdmin instances are not expected to be long-lived.  For
66   * example, an HBaseAdmin instance will not ride over a Master restart.
67   */
68  public class HBaseAdmin implements Abortable {
69    private final Log LOG = LogFactory.getLog(this.getClass().getName());
70  //  private final HConnection connection;
71    final HConnection connection;
72    private volatile Configuration conf;
73    private final long pause;
74    private final int numRetries;
75    // Some operations can take a long time such as disable of big table.
76    // numRetries is for 'normal' stuff... Mutliply by this factor when
77    // want to wait a long time.
78    private final int retryLongerMultiplier;
79  
80    /**
81     * Constructor
82     *
83     * @param c Configuration object
84     * @throws MasterNotRunningException if the master is not running
85     * @throws ZooKeeperConnectionException if unable to connect to zookeeper
86     */
87    public HBaseAdmin(Configuration c)
88    throws MasterNotRunningException, ZooKeeperConnectionException {
89      this.conf = HBaseConfiguration.create(c);
90      this.connection = HConnectionManager.getConnection(this.conf);
91      this.pause = this.conf.getLong("hbase.client.pause", 1000);
92      this.numRetries = this.conf.getInt("hbase.client.retries.number", 10);
93      this.retryLongerMultiplier = this.conf.getInt("hbase.client.retries.longer.multiplier", 10);
94      this.connection.getMaster();
95    }
96  
97    /**
98     * @return A new CatalogTracker instance; call {@link #cleanupCatalogTracker(CatalogTracker)}
99     * to cleanup the returned catalog tracker.
100    * @throws ZooKeeperConnectionException
101    * @throws IOException
102    * @see #cleanupCatalogTracker(CatalogTracker);
103    */
104   private synchronized CatalogTracker getCatalogTracker()
105   throws ZooKeeperConnectionException, IOException {
106     CatalogTracker ct = null;
107     try {
108       HConnection connection =
109         HConnectionManager.getConnection(this.conf);
110       ct = new CatalogTracker(connection);
111       ct.start();
112     } catch (InterruptedException e) {
113       // Let it out as an IOE for now until we redo all so tolerate IEs
114       Thread.currentThread().interrupt();
115       throw new IOException("Interrupted", e);
116     }
117     return ct;
118   }
119 
120   private void cleanupCatalogTracker(final CatalogTracker ct) {
121     ct.stop();
122   }
123 
124   @Override
125   public void abort(String why, Throwable e) {
126     // Currently does nothing but throw the passed message and exception
127     throw new RuntimeException(why, e);
128   }
129 
130   /** @return HConnection used by this object. */
131   public HConnection getConnection() {
132     return connection;
133   }
134 
135   /**
136    * Get a connection to the currently set master.
137    * @return proxy connection to master server for this instance
138    * @throws MasterNotRunningException if the master is not running
139    * @throws ZooKeeperConnectionException if unable to connect to zookeeper
140    */
141   public HMasterInterface getMaster()
142   throws MasterNotRunningException, ZooKeeperConnectionException {
143     return this.connection.getMaster();
144   }
145 
146   /** @return - true if the master server is running
147    * @throws ZooKeeperConnectionException
148    * @throws MasterNotRunningException */
149   public boolean isMasterRunning()
150   throws MasterNotRunningException, ZooKeeperConnectionException {
151     return this.connection.isMasterRunning();
152   }
153 
154   /**
155    * @param tableName Table to check.
156    * @return True if table exists already.
157    * @throws IOException 
158    */
159   public boolean tableExists(final String tableName)
160   throws IOException {
161     boolean b = false;
162     CatalogTracker ct = getCatalogTracker();
163     try {
164       b = MetaReader.tableExists(ct, tableName);
165     } finally {
166       cleanupCatalogTracker(ct);
167     }
168     return b;
169   }
170 
171   /**
172    * @param tableName Table to check.
173    * @return True if table exists already.
174    * @throws IOException 
175    */
176   public boolean tableExists(final byte [] tableName)
177   throws IOException {
178     return tableExists(Bytes.toString(tableName));
179   }
180 
181   /**
182    * List all the userspace tables.  In other words, scan the META table.
183    *
184    * If we wanted this to be really fast, we could implement a special
185    * catalog table that just contains table names and their descriptors.
186    * Right now, it only exists as part of the META table's region info.
187    *
188    * @return - returns an array of HTableDescriptors
189    * @throws IOException if a remote or network exception occurs
190    */
191   public HTableDescriptor[] listTables() throws IOException {
192     return this.connection.listTables();
193   }
194 
195 
196   /**
197    * Method for getting the tableDescriptor
198    * @param tableName as a byte []
199    * @return the tableDescriptor
200    * @throws IOException if a remote or network exception occurs
201    */
202   public HTableDescriptor getTableDescriptor(final byte [] tableName)
203   throws IOException {
204     return this.connection.getHTableDescriptor(tableName);
205   }
206 
207   private long getPauseTime(int tries) {
208     int triesCount = tries;
209     if (triesCount >= HConstants.RETRY_BACKOFF.length) {
210       triesCount = HConstants.RETRY_BACKOFF.length - 1;
211     }
212     return this.pause * HConstants.RETRY_BACKOFF[triesCount];
213   }
214 
215   /**
216    * Creates a new table.
217    * Synchronous operation.
218    *
219    * @param desc table descriptor for table
220    *
221    * @throws IllegalArgumentException if the table name is reserved
222    * @throws MasterNotRunningException if master is not running
223    * @throws TableExistsException if table already exists (If concurrent
224    * threads, the table may have been created between test-for-existence
225    * and attempt-at-creation).
226    * @throws IOException if a remote or network exception occurs
227    */
228   public void createTable(HTableDescriptor desc)
229   throws IOException {
230     createTable(desc, null);
231   }
232 
233   /**
234    * Creates a new table with the specified number of regions.  The start key
235    * specified will become the end key of the first region of the table, and
236    * the end key specified will become the start key of the last region of the
237    * table (the first region has a null start key and the last region has a
238    * null end key).
239    *
240    * BigInteger math will be used to divide the key range specified into
241    * enough segments to make the required number of total regions.
242    *
243    * Synchronous operation.
244    *
245    * @param desc table descriptor for table
246    * @param startKey beginning of key range
247    * @param endKey end of key range
248    * @param numRegions the total number of regions to create
249    *
250    * @throws IllegalArgumentException if the table name is reserved
251    * @throws MasterNotRunningException if master is not running
252    * @throws TableExistsException if table already exists (If concurrent
253    * threads, the table may have been created between test-for-existence
254    * and attempt-at-creation).
255    * @throws IOException
256    */
257   public void createTable(HTableDescriptor desc, byte [] startKey,
258       byte [] endKey, int numRegions)
259   throws IOException {
260     HTableDescriptor.isLegalTableName(desc.getName());
261     if(numRegions < 3) {
262       throw new IllegalArgumentException("Must create at least three regions");
263     } else if(Bytes.compareTo(startKey, endKey) >= 0) {
264       throw new IllegalArgumentException("Start key must be smaller than end key");
265     }
266     byte [][] splitKeys = Bytes.split(startKey, endKey, numRegions - 3);
267     if(splitKeys == null || splitKeys.length != numRegions - 1) {
268       throw new IllegalArgumentException("Unable to split key range into enough regions");
269     }
270     createTable(desc, splitKeys);
271   }
272 
273   /**
274    * Creates a new table with an initial set of empty regions defined by the
275    * specified split keys.  The total number of regions created will be the
276    * number of split keys plus one (the first region has a null start key and
277    * the last region has a null end key).
278    * Synchronous operation.
279    *
280    * @param desc table descriptor for table
281    * @param splitKeys array of split keys for the initial regions of the table
282    *
283    * @throws IllegalArgumentException if the table name is reserved
284    * @throws MasterNotRunningException if master is not running
285    * @throws TableExistsException if table already exists (If concurrent
286    * threads, the table may have been created between test-for-existence
287    * and attempt-at-creation).
288    * @throws IOException
289    */
290   public void createTable(final HTableDescriptor desc, byte [][] splitKeys)
291   throws IOException {
292     HTableDescriptor.isLegalTableName(desc.getName());
293     try {
294       createTableAsync(desc, splitKeys);
295     } catch (SocketTimeoutException ste) {
296       LOG.warn("Creating " + desc.getNameAsString() + " took too long", ste);
297     }
298     int numRegs = splitKeys == null ? 1 : splitKeys.length + 1;
299     int prevRegCount = 0;
300     for (int tries = 0; tries < numRetries; ++tries) {
301       // Wait for new table to come on-line
302       final AtomicInteger actualRegCount = new AtomicInteger(0);
303       MetaScannerVisitor visitor = new MetaScannerVisitor() {
304         @Override
305         public boolean processRow(Result rowResult) throws IOException {
306           HRegionInfo info = Writables.getHRegionInfoOrNull(rowResult.getValue(
307               HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER));
308           
309           //If regioninfo is null, skip this row
310           if (null == info) {
311             return true;
312           }
313           if (!(Bytes.equals(info.getTableDesc().getName(), desc.getName()))) {
314             return false;
315           }
316           String hostAndPort = null;
317           byte [] value = rowResult.getValue(HConstants.CATALOG_FAMILY,
318               HConstants.SERVER_QUALIFIER);
319           // Make sure that regions are assigned to server
320           if (value != null && value.length > 0) {
321             hostAndPort = Bytes.toString(value);
322           }
323           if (!(info.isOffline() || info.isSplit()) && hostAndPort != null) {
324             actualRegCount.incrementAndGet();
325           }
326           return true;
327         }
328       };
329       MetaScanner.metaScan(conf, visitor, desc.getName());
330       if (actualRegCount.get() != numRegs) {
331         if (tries == numRetries - 1) {
332           throw new RegionOfflineException("Only " + actualRegCount.get() + 
333               " of " + numRegs + " regions are online; retries exhausted.");
334         }
335         try { // Sleep
336           Thread.sleep(getPauseTime(tries));
337         } catch (InterruptedException e) {
338           throw new InterruptedIOException("Interrupted when opening" +
339               " regions; " + actualRegCount.get() + " of " + numRegs + 
340               " regions processed so far");
341         }
342         if (actualRegCount.get() > prevRegCount) { // Making progress
343           prevRegCount = actualRegCount.get();
344           tries = -1;
345         }
346       } else {
347         return;
348       }
349     }
350   }
351 
352   /**
353    * Creates a new table but does not block and wait for it to come online.
354    * Asynchronous operation.
355    *
356    * @param desc table descriptor for table
357    *
358    * @throws IllegalArgumentException Bad table name.
359    * @throws MasterNotRunningException if master is not running
360    * @throws TableExistsException if table already exists (If concurrent
361    * threads, the table may have been created between test-for-existence
362    * and attempt-at-creation).
363    * @throws IOException
364    */
365   public void createTableAsync(HTableDescriptor desc, byte [][] splitKeys)
366   throws IOException {
367     HTableDescriptor.isLegalTableName(desc.getName());
368     if(splitKeys != null && splitKeys.length > 1) {
369       Arrays.sort(splitKeys, Bytes.BYTES_COMPARATOR);
370       // Verify there are no duplicate split keys
371       byte [] lastKey = null;
372       for(byte [] splitKey : splitKeys) {
373         if(lastKey != null && Bytes.equals(splitKey, lastKey)) {
374           throw new IllegalArgumentException("All split keys must be unique, " +
375             "found duplicate: " + Bytes.toStringBinary(splitKey) +
376             ", " + Bytes.toStringBinary(lastKey));
377         }
378         lastKey = splitKey;
379       }
380     }
381     try {
382       getMaster().createTable(desc, splitKeys);
383     } catch (RemoteException e) {
384       throw e.unwrapRemoteException();
385     }
386   }
387 
388   /**
389    * Deletes a table.
390    * Synchronous operation.
391    *
392    * @param tableName name of table to delete
393    * @throws IOException if a remote or network exception occurs
394    */
395   public void deleteTable(final String tableName) throws IOException {
396     deleteTable(Bytes.toBytes(tableName));
397   }
398 
399   /**
400    * Deletes a table.
401    * Synchronous operation.
402    *
403    * @param tableName name of table to delete
404    * @throws IOException if a remote or network exception occurs
405    */
406   public void deleteTable(final byte [] tableName) throws IOException {
407     isMasterRunning();
408     HTableDescriptor.isLegalTableName(tableName);
409     HRegionLocation firstMetaServer = getFirstMetaServerForTable(tableName);
410     try {
411       getMaster().deleteTable(tableName);
412     } catch (RemoteException e) {
413       throw RemoteExceptionHandler.decodeRemoteException(e);
414     }
415     // Wait until all regions deleted
416     HRegionInterface server =
417       connection.getHRegionConnection(firstMetaServer.getServerAddress());
418     for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
419       long scannerId = -1L;
420       try {
421 
422         Scan scan = MetaReader.getScanForTableName(tableName);
423         scan.addColumn(HConstants.CATALOG_FAMILY,
424             HConstants.REGIONINFO_QUALIFIER);
425         scannerId = server.openScanner(
426           firstMetaServer.getRegionInfo().getRegionName(), scan);
427         // Get a batch at a time.
428         Result values = server.next(scannerId);
429         if (values == null) {
430           break;
431         }
432       } catch (IOException ex) {
433         if(tries == numRetries - 1) {           // no more tries left
434           if (ex instanceof RemoteException) {
435             ex = RemoteExceptionHandler.decodeRemoteException((RemoteException) ex);
436           }
437           throw ex;
438         }
439       } finally {
440         if (scannerId != -1L) {
441           try {
442             server.close(scannerId);
443           } catch (Exception ex) {
444             LOG.warn(ex);
445           }
446         }
447       }
448       try {
449         Thread.sleep(getPauseTime(tries));
450       } catch (InterruptedException e) {
451         // continue
452       }
453     }
454     // Delete cached information to prevent clients from using old locations
455     this.connection.clearRegionCache(tableName);
456     LOG.info("Deleted " + Bytes.toString(tableName));
457   }
458 
459   public void enableTable(final String tableName)
460   throws IOException {
461     enableTable(Bytes.toBytes(tableName));
462   }
463 
464   /**
465    * Enable a table.  May timeout.  Use {@link #enableTableAsync(byte[])}
466    * and {@link #isTableEnabled(byte[])} instead.
467    * @param tableName name of the table
468    * @throws IOException if a remote or network exception occurs
469    * @see #isTableEnabled(byte[])
470    * @see #disableTable(byte[])
471    * @see #enableTableAsync(byte[])
472    */
473   public void enableTable(final byte [] tableName)
474   throws IOException {
475     enableTableAsync(tableName);
476  
477     // Wait until all regions are enabled
478     boolean enabled = false;
479     for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
480       enabled = isTableEnabled(tableName);
481       if (enabled) {
482         break;
483       }
484       long sleep = getPauseTime(tries);
485       if (LOG.isDebugEnabled()) {
486         LOG.debug("Sleeping= " + sleep + "ms, waiting for all regions to be " +
487           "enabled in " + Bytes.toString(tableName));
488       }
489       try {
490         Thread.sleep(sleep);
491       } catch (InterruptedException e) {
492         Thread.currentThread().interrupt();
493         // Do this conversion rather than let it out because do not want to
494         // change the method signature.
495         throw new IOException("Interrupted", e);
496       }
497     }
498     if (!enabled) {
499       throw new IOException("Unable to enable table " +
500         Bytes.toString(tableName));
501     }
502     LOG.info("Enabled table " + Bytes.toString(tableName));
503   }
504 
505   public void enableTableAsync(final String tableName)
506   throws IOException {
507     enableTableAsync(Bytes.toBytes(tableName));
508   }
509 
510   /**
511    * Brings a table on-line (enables it).  Method returns immediately though
512    * enable of table may take some time to complete, especially if the table
513    * is large (All regions are opened as part of enabling process).  Check
514    * {@link #isTableEnabled(byte[])} to learn when table is fully online.  If
515    * table is taking too long to online, check server logs.
516    * @param tableName
517    * @throws IOException
518    * @since 0.90.0
519    */
520   public void enableTableAsync(final byte [] tableName)
521   throws IOException {
522     isMasterRunning();
523     try {
524       getMaster().enableTable(tableName);
525     } catch (RemoteException e) {
526       throw e.unwrapRemoteException();
527     }
528     LOG.info("Started enable of " + Bytes.toString(tableName));
529   }
530 
531   public void disableTableAsync(final String tableName) throws IOException {
532     disableTableAsync(Bytes.toBytes(tableName));
533   }
534 
535   /**
536    * Starts the disable of a table.  If it is being served, the master
537    * will tell the servers to stop serving it.  This method returns immediately.
538    * The disable of a table can take some time if the table is large (all
539    * regions are closed as part of table disable operation).
540    * Call {@link #isTableDisabled(byte[])} to check for when disable completes.
541    * If table is taking too long to online, check server logs.
542    * @param tableName name of table
543    * @throws IOException if a remote or network exception occurs
544    * @see #isTableDisabled(byte[])
545    * @see #isTableEnabled(byte[])
546    * @since 0.90.0
547    */
548   public void disableTableAsync(final byte [] tableName) throws IOException {
549     isMasterRunning();
550     try {
551       getMaster().disableTable(tableName);
552     } catch (RemoteException e) {
553       throw e.unwrapRemoteException();
554     }
555     LOG.info("Started disable of " + Bytes.toString(tableName));
556   }
557 
558   public void disableTable(final String tableName)
559   throws IOException {
560     disableTable(Bytes.toBytes(tableName));
561   }
562 
563   /**
564    * Disable table and wait on completion.  May timeout eventually.  Use
565    * {@link #disableTableAsync(byte[])} and {@link #isTableDisabled(String)}
566    * instead.
567    * @param tableName
568    * @throws IOException
569    */
570   public void disableTable(final byte [] tableName)
571   throws IOException {
572     disableTableAsync(tableName);
573     // Wait until table is disabled
574     boolean disabled = false;
575     for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
576       disabled = isTableDisabled(tableName);
577       if (disabled) {
578         break;
579       }
580       long sleep = getPauseTime(tries);
581       if (LOG.isDebugEnabled()) {
582         LOG.debug("Sleeping= " + sleep + "ms, waiting for all regions to be " +
583           "disabled in " + Bytes.toString(tableName));
584       }
585       try {
586         Thread.sleep(sleep);
587       } catch (InterruptedException e) {
588         // Do this conversion rather than let it out because do not want to
589         // change the method signature.
590         Thread.currentThread().interrupt();
591         throw new IOException("Interrupted", e);
592       }
593     }
594     if (!disabled) {
595       throw new RegionException("Retries exhausted, it took too long to wait"+
596         " for the table " + Bytes.toString(tableName) + " to be disabled.");
597     }
598     LOG.info("Disabled " + Bytes.toString(tableName));
599   }
600 
601   /**
602    * @param tableName name of table to check
603    * @return true if table is on-line
604    * @throws IOException if a remote or network exception occurs
605    */
606   public boolean isTableEnabled(String tableName) throws IOException {
607     return isTableEnabled(Bytes.toBytes(tableName));
608   }
609   /**
610    * @param tableName name of table to check
611    * @return true if table is on-line
612    * @throws IOException if a remote or network exception occurs
613    */
614   public boolean isTableEnabled(byte[] tableName) throws IOException {
615     return connection.isTableEnabled(tableName);
616   }
617 
618   /**
619    * @param tableName name of table to check
620    * @return true if table is off-line
621    * @throws IOException if a remote or network exception occurs
622    */
623   public boolean isTableDisabled(final String tableName) throws IOException {
624     return isTableDisabled(Bytes.toBytes(tableName));
625   }
626 
627   /**
628    * @param tableName name of table to check
629    * @return true if table is off-line
630    * @throws IOException if a remote or network exception occurs
631    */
632   public boolean isTableDisabled(byte[] tableName) throws IOException {
633     return connection.isTableDisabled(tableName);
634   }
635 
636   /**
637    * @param tableName name of table to check
638    * @return true if all regions of the table are available
639    * @throws IOException if a remote or network exception occurs
640    */
641   public boolean isTableAvailable(byte[] tableName) throws IOException {
642     return connection.isTableAvailable(tableName);
643   }
644 
645   /**
646    * @param tableName name of table to check
647    * @return true if all regions of the table are available
648    * @throws IOException if a remote or network exception occurs
649    */
650   public boolean isTableAvailable(String tableName) throws IOException {
651     return connection.isTableAvailable(Bytes.toBytes(tableName));
652   }
653 
654   /**
655    * Add a column to an existing table.
656    * Asynchronous operation.
657    *
658    * @param tableName name of the table to add column to
659    * @param column column descriptor of column to be added
660    * @throws IOException if a remote or network exception occurs
661    */
662   public void addColumn(final String tableName, HColumnDescriptor column)
663   throws IOException {
664     addColumn(Bytes.toBytes(tableName), column);
665   }
666 
667   /**
668    * Add a column to an existing table.
669    * Asynchronous operation.
670    *
671    * @param tableName name of the table to add column to
672    * @param column column descriptor of column to be added
673    * @throws IOException if a remote or network exception occurs
674    */
675   public void addColumn(final byte [] tableName, HColumnDescriptor column)
676   throws IOException {
677     HTableDescriptor.isLegalTableName(tableName);
678     try {
679       getMaster().addColumn(tableName, column);
680     } catch (RemoteException e) {
681       throw RemoteExceptionHandler.decodeRemoteException(e);
682     }
683   }
684 
685   /**
686    * Delete a column from a table.
687    * Asynchronous operation.
688    *
689    * @param tableName name of table
690    * @param columnName name of column to be deleted
691    * @throws IOException if a remote or network exception occurs
692    */
693   public void deleteColumn(final String tableName, final String columnName)
694   throws IOException {
695     deleteColumn(Bytes.toBytes(tableName), Bytes.toBytes(columnName));
696   }
697 
698   /**
699    * Delete a column from a table.
700    * Asynchronous operation.
701    *
702    * @param tableName name of table
703    * @param columnName name of column to be deleted
704    * @throws IOException if a remote or network exception occurs
705    */
706   public void deleteColumn(final byte [] tableName, final byte [] columnName)
707   throws IOException {
708     try {
709       getMaster().deleteColumn(tableName, columnName);
710     } catch (RemoteException e) {
711       throw RemoteExceptionHandler.decodeRemoteException(e);
712     }
713   }
714 
715   /**
716    * Modify an existing column family on a table.
717    * Asynchronous operation.
718    *
719    * @param tableName name of table
720    * @param columnName name of column to be modified
721    * @param descriptor new column descriptor to use
722    * @throws IOException if a remote or network exception occurs
723    * @deprecated The <code>columnName</code> is redundant. Use {@link #addColumn(String, HColumnDescriptor)}
724    */
725   public void modifyColumn(final String tableName, final String columnName,
726       HColumnDescriptor descriptor)
727   throws IOException {
728     modifyColumn(tableName,  descriptor);
729   }
730 
731   /**
732    * Modify an existing column family on a table.
733    * Asynchronous operation.
734    *
735    * @param tableName name of table
736    * @param descriptor new column descriptor to use
737    * @throws IOException if a remote or network exception occurs
738    */
739   public void modifyColumn(final String tableName, HColumnDescriptor descriptor)
740   throws IOException {
741     modifyColumn(Bytes.toBytes(tableName), descriptor);
742   }
743 
744   /**
745    * Modify an existing column family on a table.
746    * Asynchronous operation.
747    *
748    * @param tableName name of table
749    * @param columnName name of column to be modified
750    * @param descriptor new column descriptor to use
751    * @throws IOException if a remote or network exception occurs
752    * @deprecated The <code>columnName</code> is redundant. Use {@link #modifyColumn(byte[], HColumnDescriptor)}
753    */
754   public void modifyColumn(final byte [] tableName, final byte [] columnName,
755     HColumnDescriptor descriptor)
756   throws IOException {
757     modifyColumn(tableName, descriptor);
758   }
759 
760   /**
761    * Modify an existing column family on a table.
762    * Asynchronous operation.
763    *
764    * @param tableName name of table
765    * @param descriptor new column descriptor to use
766    * @throws IOException if a remote or network exception occurs
767    */
768   public void modifyColumn(final byte [] tableName, HColumnDescriptor descriptor)
769   throws IOException {
770     try {
771       getMaster().modifyColumn(tableName, descriptor);
772     } catch (RemoteException re) {
773       // Convert RE exceptions in here; client shouldn't have to deal with them,
774       // at least w/ the type of exceptions that come out of this method:
775       // TableNotFoundException, etc.
776       throw RemoteExceptionHandler.decodeRemoteException(re);
777     }
778   }
779 
780   /**
781    * Close a region. For expert-admins.  Runs close on the regionserver.  The
782    * master will not be informed of the close.
783    * @param regionname region name to close
784    * @param hostAndPort If supplied, we'll use this location rather than
785    * the one currently in <code>.META.</code>
786    * @throws IOException if a remote or network exception occurs
787    */
788   public void closeRegion(final String regionname, final String hostAndPort)
789   throws IOException {
790     closeRegion(Bytes.toBytes(regionname), hostAndPort);
791   }
792 
793   /**
794    * Close a region.  For expert-admins  Runs close on the regionserver.  The
795    * master will not be informed of the close.
796    * @param regionname region name to close
797    * @param hostAndPort If supplied, we'll use this location rather than
798    * the one currently in <code>.META.</code>
799    * @throws IOException if a remote or network exception occurs
800    */
801   public void closeRegion(final byte [] regionname, final String hostAndPort)
802   throws IOException {
803     CatalogTracker ct = getCatalogTracker();
804     try {
805       if (hostAndPort != null) {
806         HServerAddress hsa = new HServerAddress(hostAndPort);
807         Pair<HRegionInfo, HServerAddress> pair =
808           MetaReader.getRegion(ct, regionname);
809         if (pair == null || pair.getSecond() == null) {
810           LOG.info("No server in .META. for " +
811             Bytes.toString(regionname) + "; pair=" + pair);
812         } else {
813           closeRegion(hsa, pair.getFirst());
814         }
815       } else {
816         Pair<HRegionInfo, HServerAddress> pair =
817           MetaReader.getRegion(ct, regionname);
818         if (pair == null || pair.getSecond() == null) {
819           LOG.info("No server in .META. for " +
820             Bytes.toStringBinary(regionname) + "; pair=" + pair);
821         } else {
822           closeRegion(pair.getSecond(), pair.getFirst());
823         }
824       }
825     } finally {
826       cleanupCatalogTracker(ct);
827     }
828   }
829 
830   public void closeRegion(final HServerAddress hsa, final HRegionInfo hri)
831   throws IOException {
832     HRegionInterface rs = this.connection.getHRegionConnection(hsa);
833     // Close the region without updating zk state.
834     rs.closeRegion(hri, false);
835   }
836 
837   /**
838    * Flush a table or an individual region.
839    * Asynchronous operation.
840    *
841    * @param tableNameOrRegionName table or region to flush
842    * @throws IOException if a remote or network exception occurs
843    * @throws InterruptedException 
844    */
845   public void flush(final String tableNameOrRegionName)
846   throws IOException, InterruptedException {
847     flush(Bytes.toBytes(tableNameOrRegionName));
848   }
849 
850   /**
851    * Flush a table or an individual region.
852    * Asynchronous operation.
853    *
854    * @param tableNameOrRegionName table or region to flush
855    * @throws IOException if a remote or network exception occurs
856    * @throws InterruptedException 
857    */
858   public void flush(final byte [] tableNameOrRegionName)
859   throws IOException, InterruptedException {
860     boolean isRegionName = isRegionName(tableNameOrRegionName);
861     CatalogTracker ct = getCatalogTracker();
862     try {
863       if (isRegionName) {
864         Pair<HRegionInfo, HServerAddress> pair =
865           MetaReader.getRegion(ct, tableNameOrRegionName);
866         if (pair == null || pair.getSecond() == null) {
867           LOG.info("No server in .META. for " +
868             Bytes.toStringBinary(tableNameOrRegionName) + "; pair=" + pair);
869         } else {
870           flush(pair.getSecond(), pair.getFirst());
871         }
872       } else {
873         List<Pair<HRegionInfo, HServerAddress>> pairs =
874           MetaReader.getTableRegionsAndLocations(ct,
875               Bytes.toString(tableNameOrRegionName));
876         for (Pair<HRegionInfo, HServerAddress> pair: pairs) {
877           if (pair.getFirst().isOffline()) continue;
878           if (pair.getSecond() == null) continue;
879           try {
880             flush(pair.getSecond(), pair.getFirst());
881           } catch (NotServingRegionException e) {
882             if (LOG.isDebugEnabled()) {
883               LOG.debug("Trying to flush " + pair.getFirst() + ": " +
884                 StringUtils.stringifyException(e));
885             }
886           }
887         }
888       }
889     } finally {
890       cleanupCatalogTracker(ct);
891     }
892   }
893 
894   private void flush(final HServerAddress hsa, final HRegionInfo hri)
895   throws IOException {
896     HRegionInterface rs = this.connection.getHRegionConnection(hsa);
897     rs.flushRegion(hri);
898   }
899 
900   /**
901    * Compact a table or an individual region.
902    * Asynchronous operation.
903    *
904    * @param tableNameOrRegionName table or region to compact
905    * @throws IOException if a remote or network exception occurs
906    * @throws InterruptedException 
907    */
908   public void compact(final String tableNameOrRegionName)
909   throws IOException, InterruptedException {
910     compact(Bytes.toBytes(tableNameOrRegionName));
911   }
912 
913   /**
914    * Compact a table or an individual region.
915    * Asynchronous operation.
916    *
917    * @param tableNameOrRegionName table or region to compact
918    * @throws IOException if a remote or network exception occurs
919    * @throws InterruptedException 
920    */
921   public void compact(final byte [] tableNameOrRegionName)
922   throws IOException, InterruptedException {
923     compact(tableNameOrRegionName, false);
924   }
925 
926   /**
927    * Major compact a table or an individual region.
928    * Asynchronous operation.
929    *
930    * @param tableNameOrRegionName table or region to major compact
931    * @throws IOException if a remote or network exception occurs
932    * @throws InterruptedException 
933    */
934   public void majorCompact(final String tableNameOrRegionName)
935   throws IOException, InterruptedException {
936     majorCompact(Bytes.toBytes(tableNameOrRegionName));
937   }
938 
939   /**
940    * Major compact a table or an individual region.
941    * Asynchronous operation.
942    *
943    * @param tableNameOrRegionName table or region to major compact
944    * @throws IOException if a remote or network exception occurs
945    * @throws InterruptedException 
946    */
947   public void majorCompact(final byte [] tableNameOrRegionName)
948   throws IOException, InterruptedException {
949     compact(tableNameOrRegionName, true);
950   }
951 
952   /**
953    * Compact a table or an individual region.
954    * Asynchronous operation.
955    *
956    * @param tableNameOrRegionName table or region to compact
957    * @param major True if we are to do a major compaction.
958    * @throws IOException if a remote or network exception occurs
959    * @throws InterruptedException 
960    */
961   private void compact(final byte [] tableNameOrRegionName, final boolean major)
962   throws IOException, InterruptedException {
963     CatalogTracker ct = getCatalogTracker();
964     try {
965       if (isRegionName(tableNameOrRegionName)) {
966         Pair<HRegionInfo, HServerAddress> pair =
967           MetaReader.getRegion(ct, tableNameOrRegionName);
968         if (pair == null || pair.getSecond() == null) {
969           LOG.info("No server in .META. for " +
970             Bytes.toStringBinary(tableNameOrRegionName) + "; pair=" + pair);
971         } else {
972           compact(pair.getSecond(), pair.getFirst(), major);
973         }
974       } else {
975         List<Pair<HRegionInfo, HServerAddress>> pairs =
976           MetaReader.getTableRegionsAndLocations(ct,
977               Bytes.toString(tableNameOrRegionName));
978         for (Pair<HRegionInfo, HServerAddress> pair: pairs) {
979           if (pair.getFirst().isOffline()) continue;
980           if (pair.getSecond() == null) continue;
981           try {
982             compact(pair.getSecond(), pair.getFirst(), major);
983           } catch (NotServingRegionException e) {
984             if (LOG.isDebugEnabled()) {
985               LOG.debug("Trying to" + (major ? " major" : "") + " compact " +
986                 pair.getFirst() + ": " +
987                 StringUtils.stringifyException(e));
988             }
989           }
990         }
991       }
992     } finally {
993       cleanupCatalogTracker(ct);
994     }
995   }
996 
997   private void compact(final HServerAddress hsa, final HRegionInfo hri,
998       final boolean major)
999   throws IOException {
1000     HRegionInterface rs = this.connection.getHRegionConnection(hsa);
1001     rs.compactRegion(hri, major);
1002   }
1003 
1004   /**
1005    * Move the region <code>r</code> to <code>dest</code>.
1006    * @param encodedRegionName The encoded region name; i.e. the hash that makes
1007    * up the region name suffix: e.g. if regionname is
1008    * <code>TestTable,0094429456,1289497600452.527db22f95c8a9e0116f0cc13c680396.</code>,
1009    * then the encoded region name is: <code>527db22f95c8a9e0116f0cc13c680396</code>.
1010    * @param destServerName The servername of the destination regionserver.  If
1011    * passed the empty byte array we'll assign to a random server.  A server name
1012    * is made of host, port and startcode.  Here is an example:
1013    * <code> host187.example.com,60020,1289493121758</code>.
1014    * @throws UnknownRegionException Thrown if we can't find a region named
1015    * <code>encodedRegionName</code>
1016    * @throws ZooKeeperConnectionException 
1017    * @throws MasterNotRunningException 
1018    */
1019   public void move(final byte [] encodedRegionName, final byte [] destServerName)
1020   throws UnknownRegionException, MasterNotRunningException, ZooKeeperConnectionException {
1021     getMaster().move(encodedRegionName, destServerName);
1022   }
1023 
1024   /**
1025    * @param regionName Region name to assign.
1026    * @param force True to force assign.
1027    * @throws MasterNotRunningException
1028    * @throws ZooKeeperConnectionException
1029    * @throws IOException
1030    */
1031   public void assign(final byte [] regionName, final boolean force)
1032   throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
1033     getMaster().assign(regionName, force);
1034   }
1035 
1036   /**
1037    * Unassign a region from current hosting regionserver.  Region will then be
1038    * assigned to a regionserver chosen at random.  Region could be reassigned
1039    * back to the same server.  Use {@link #move(byte[], byte[])} if you want
1040    * to control the region movement.
1041    * @param regionName Region to unassign. Will clear any existing RegionPlan
1042    * if one found.
1043    * @param force If true, force unassign (Will remove region from
1044    * regions-in-transition too if present).
1045    * @throws MasterNotRunningException
1046    * @throws ZooKeeperConnectionException
1047    * @throws IOException
1048    */
1049   public void unassign(final byte [] regionName, final boolean force)
1050   throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
1051     getMaster().unassign(regionName, force);
1052   }
1053 
1054   /**
1055    * Turn the load balancer on or off.
1056    * @param b If true, enable balancer. If false, disable balancer.
1057    * @return Previous balancer value
1058    */
1059   public boolean balanceSwitch(final boolean b)
1060   throws MasterNotRunningException, ZooKeeperConnectionException {
1061     return getMaster().balanceSwitch(b);
1062   }
1063 
1064   /**
1065    * Invoke the balancer.  Will run the balancer and if regions to move, it will
1066    * go ahead and do the reassignments.  Can NOT run for various reasons.  Check
1067    * logs.
1068    * @return True if balancer ran, false otherwise.
1069    */
1070   public boolean balancer()
1071   throws MasterNotRunningException, ZooKeeperConnectionException {
1072     return getMaster().balance();
1073   }
1074 
1075   /**
1076    * Split a table or an individual region.
1077    * Asynchronous operation.
1078    *
1079    * @param tableNameOrRegionName table or region to split
1080    * @throws IOException if a remote or network exception occurs
1081    * @throws InterruptedException 
1082    */
1083   public void split(final String tableNameOrRegionName)
1084   throws IOException, InterruptedException {
1085     split(Bytes.toBytes(tableNameOrRegionName));
1086   }
1087 
1088   /**
1089    * Split a table or an individual region.  Implicitly finds an optimal split
1090    * point.  Asynchronous operation.
1091    *
1092    * @param tableNameOrRegionName table to region to split
1093    * @throws IOException if a remote or network exception occurs
1094    * @throws InterruptedException 
1095    */
1096   public void split(final byte [] tableNameOrRegionName)
1097   throws IOException, InterruptedException {
1098     split(tableNameOrRegionName, null);
1099   }
1100 
1101   /**
1102    * Split a table or an individual region.
1103    * Asynchronous operation.
1104    *
1105    * @param tableNameOrRegionName table to region to split
1106    * @param splitPoint the explicit position to split on
1107    * @throws IOException if a remote or network exception occurs
1108    * @throws InterruptedException interrupt exception occurred
1109    */
1110   public void split(final byte [] tableNameOrRegionName,
1111       final byte [] splitPoint) throws IOException, InterruptedException {
1112     CatalogTracker ct = getCatalogTracker();
1113     try {
1114       if (isRegionName(tableNameOrRegionName)) {
1115         // Its a possible region name.
1116         Pair<HRegionInfo, HServerAddress> pair =
1117           MetaReader.getRegion(ct, tableNameOrRegionName);
1118         if (pair == null || pair.getSecond() == null) {
1119           LOG.info("No server in .META. for " +
1120             Bytes.toStringBinary(tableNameOrRegionName) + "; pair=" + pair);
1121         } else {
1122           split(pair.getSecond(), pair.getFirst(), splitPoint);
1123         }
1124       } else {
1125         List<Pair<HRegionInfo, HServerAddress>> pairs =
1126           MetaReader.getTableRegionsAndLocations(ct,
1127               Bytes.toString(tableNameOrRegionName));
1128         for (Pair<HRegionInfo, HServerAddress> pair: pairs) {
1129           // May not be a server for a particular row
1130           if (pair.getSecond() == null) continue;
1131           HRegionInfo r = pair.getFirst();
1132           // check for parents
1133           if (r.isSplitParent()) continue;
1134           if (splitPoint != null) {
1135             // if a split point given, only split that particular region
1136             if (!r.containsRow(splitPoint)) continue;
1137           }
1138           split(pair.getSecond(), pair.getFirst(), splitPoint);
1139         }
1140       }
1141     } finally {
1142       cleanupCatalogTracker(ct);
1143     }
1144   }
1145 
1146   private void split(final HServerAddress hsa, final HRegionInfo hri,
1147       byte[] splitPoint) throws IOException {
1148     HRegionInterface rs = this.connection.getHRegionConnection(hsa);
1149     rs.splitRegion(hri, splitPoint);
1150   }
1151 
1152   /**
1153    * Modify an existing table, more IRB friendly version.
1154    * Asynchronous operation.  This means that it may be a while before your
1155    * schema change is updated across all of the table.
1156    *
1157    * @param tableName name of table.
1158    * @param htd modified description of the table
1159    * @throws IOException if a remote or network exception occurs
1160    */
1161   public void modifyTable(final byte [] tableName, HTableDescriptor htd)
1162   throws IOException {
1163     try {
1164       getMaster().modifyTable(tableName, htd);
1165     } catch (RemoteException re) {
1166       // Convert RE exceptions in here; client shouldn't have to deal with them,
1167       // at least w/ the type of exceptions that come out of this method:
1168       // TableNotFoundException, etc.
1169       throw RemoteExceptionHandler.decodeRemoteException(re);
1170     }
1171   }
1172 
1173   /**
1174    * @param tableNameOrRegionName Name of a table or name of a region.
1175    * @return True if <code>tableNameOrRegionName</code> is *possibly* a region
1176    * name else false if a verified tablename (we call {@link #tableExists(byte[])};
1177    * else we throw an exception.
1178    * @throws IOException 
1179    */
1180   private boolean isRegionName(final byte [] tableNameOrRegionName)
1181   throws IOException {
1182     if (tableNameOrRegionName == null) {
1183       throw new IllegalArgumentException("Pass a table name or region name");
1184     }
1185     return !tableExists(tableNameOrRegionName);
1186   }
1187 
1188   /**
1189    * Shuts down the HBase cluster
1190    * @throws IOException if a remote or network exception occurs
1191    */
1192   public synchronized void shutdown() throws IOException {
1193     isMasterRunning();
1194     try {
1195       getMaster().shutdown();
1196     } catch (RemoteException e) {
1197       throw RemoteExceptionHandler.decodeRemoteException(e);
1198     }
1199   }
1200 
1201   /**
1202    * Shuts down the current HBase master only.
1203    * Does not shutdown the cluster.
1204    * @see #shutdown()
1205    * @throws IOException if a remote or network exception occurs
1206    */
1207   public synchronized void stopMaster() throws IOException {
1208     isMasterRunning();
1209     try {
1210       getMaster().stopMaster();
1211     } catch (RemoteException e) {
1212       throw RemoteExceptionHandler.decodeRemoteException(e);
1213     }
1214   }
1215 
1216   /**
1217    * Stop the designated regionserver.
1218    * @throws IOException if a remote or network exception occurs
1219    */
1220   public synchronized void stopRegionServer(final HServerAddress hsa)
1221   throws IOException {
1222     HRegionInterface rs = this.connection.getHRegionConnection(hsa);
1223     rs.stop("Called by admin client " + this.connection.toString());
1224   }
1225 
1226   /**
1227    * @return cluster status
1228    * @throws IOException if a remote or network exception occurs
1229    */
1230   public ClusterStatus getClusterStatus() throws IOException {
1231     return getMaster().getClusterStatus();
1232   }
1233 
1234   private HRegionLocation getFirstMetaServerForTable(final byte [] tableName)
1235   throws IOException {
1236     return connection.locateRegion(HConstants.META_TABLE_NAME,
1237       HRegionInfo.createRegionName(tableName, null, HConstants.NINES, false));
1238   }
1239 
1240   /**
1241    * @return Configuration used by the instance.
1242    */
1243   public Configuration getConfiguration() {
1244     return this.conf;
1245   }
1246 
1247   /**
1248    * Check to see if HBase is running. Throw an exception if not.
1249    *
1250    * @param conf system configuration
1251    * @throws MasterNotRunningException if the master is not running
1252    * @throws ZooKeeperConnectionException if unable to connect to zookeeper
1253    */
1254   public static void checkHBaseAvailable(Configuration conf)
1255   throws MasterNotRunningException, ZooKeeperConnectionException {
1256     Configuration copyOfConf = HBaseConfiguration.create(conf);
1257     copyOfConf.setInt("hbase.client.retries.number", 1);
1258     new HBaseAdmin(copyOfConf);
1259   }
1260 }