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