/**
* Copyright 2011 The Apache Software Foundation
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.client;
import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Abortable;
import org.apache.hadoop.hbase.ClusterStatus;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.MasterNotRunningException;
import org.apache.hadoop.hbase.NotServingRegionException;
import org.apache.hadoop.hbase.RegionException;
import org.apache.hadoop.hbase.RemoteExceptionHandler;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableExistsException;
import org.apache.hadoop.hbase.TableNotEnabledException;
import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.UnknownRegionException;
import org.apache.hadoop.hbase.ZooKeeperConnectionException;
import org.apache.hadoop.hbase.catalog.CatalogTracker;
import org.apache.hadoop.hbase.catalog.MetaReader;
import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitorBase;
import org.apache.hadoop.hbase.ipc.CoprocessorProtocol;
import org.apache.hadoop.hbase.ipc.HMasterInterface;
import org.apache.hadoop.hbase.ipc.HRegionInterface;
import org.apache.hadoop.hbase.ipc.MasterExecRPCInvoker;
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest.CompactionState;
import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
import org.apache.hadoop.hbase.snapshot.HSnapshotDescription;
import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
import org.apache.hadoop.hbase.util.Addressing;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.Writables;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.util.StringUtils;
/**
* Provides an interface to manage HBase database table metadata + general
* administrative functions. Use HBaseAdmin to create, drop, list, enable and
* disable tables. Use it also to add and drop table column families.
*
* <p>See {@link HTable} to add, update, and delete data from an individual table.
* <p>Currently HBaseAdmin instances are not expected to be long-lived. For
* example, an HBaseAdmin instance will not ride over a Master restart.
*/
public class HBaseAdmin implements Abortable, Closeable {
private final Log LOG = LogFactory.getLog(this.getClass().getName());
// private final HConnection connection;
private HConnection connection;
private volatile Configuration conf;
private final long pause;
private final int numRetries;
// Some operations can take a long time such as disable of big table.
// numRetries is for 'normal' stuff... Mutliply by this factor when
// want to wait a long time.
private final int retryLongerMultiplier;
private boolean aborted;
static final String INDEX_TABLE_SUFFIX = "_idx";
private static volatile boolean synchronousBalanceSwitchSupported = true;
/**
* Constructor
*
* @param c Configuration object
* @throws MasterNotRunningException if the master is not running
* @throws ZooKeeperConnectionException if unable to connect to zookeeper
*/
public HBaseAdmin(Configuration c)
throws MasterNotRunningException, ZooKeeperConnectionException {
this.conf = HBaseConfiguration.create(c);
this.connection = HConnectionManager.getConnection(this.conf);
this.pause = this.conf.getLong("hbase.client.pause", 1000);
this.numRetries = this.conf.getInt("hbase.client.retries.number", 10);
this.retryLongerMultiplier = this.conf.getInt(
"hbase.client.retries.longer.multiplier", 10);
int tries = 0;
while ( true ){
try {
this.connection.getMaster();
return;
} catch (MasterNotRunningException mnre) {
HConnectionManager.deleteStaleConnection(this.connection);
this.connection = HConnectionManager.getConnection(this.conf);
}
tries++;
if (tries >= numRetries) {
// we should delete connection between client and zookeeper
HConnectionManager.deleteStaleConnection(this.connection);
throw new MasterNotRunningException("Retried " + numRetries + " times");
}
try {
Thread.sleep(getPauseTime(tries));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// we should delete connection between client and zookeeper
HConnectionManager.deleteStaleConnection(this.connection);
throw new MasterNotRunningException(
"Interrupted after "+tries+" tries");
}
}
}
/**
* Constructor for externally managed HConnections.
* This constructor fails fast if the HMaster is not running.
* The HConnection can be re-used again in another attempt.
* This constructor fails fast.
*
* @param connection The HConnection instance to use
* @throws MasterNotRunningException if the master is not running
* @throws ZooKeeperConnectionException if unable to connect to zookeeper
*/
public HBaseAdmin(HConnection connection)
throws MasterNotRunningException, ZooKeeperConnectionException {
this.conf = connection.getConfiguration();
this.connection = connection;
this.pause = this.conf.getLong("hbase.client.pause", 1000);
this.numRetries = this.conf.getInt("hbase.client.retries.number", 10);
this.retryLongerMultiplier = this.conf.getInt(
"hbase.client.retries.longer.multiplier", 10);
this.connection.getMaster();
}
/**
* @return A new CatalogTracker instance; call {@link #cleanupCatalogTracker(CatalogTracker)}
* to cleanup the returned catalog tracker.
* @throws ZooKeeperConnectionException
* @throws IOException
* @see #cleanupCatalogTracker(CatalogTracker)
*/
private synchronized CatalogTracker getCatalogTracker()
throws ZooKeeperConnectionException, IOException {
CatalogTracker ct = null;
try {
ct = new CatalogTracker(this.conf);
ct.start();
} catch (InterruptedException e) {
// Let it out as an IOE for now until we redo all so tolerate IEs
Thread.currentThread().interrupt();
throw new IOException("Interrupted", e);
}
return ct;
}
private void cleanupCatalogTracker(final CatalogTracker ct) {
ct.stop();
}
@Override
public void abort(String why, Throwable e) {
// Currently does nothing but throw the passed message and exception
this.aborted = true;
throw new RuntimeException(why, e);
}
@Override
public boolean isAborted(){
return this.aborted;
}
/** @return HConnection used by this object. */
public HConnection getConnection() {
return connection;
}
/**
* Get a connection to the currently set master.
* @return proxy connection to master server for this instance
* @throws MasterNotRunningException if the master is not running
* @throws ZooKeeperConnectionException if unable to connect to zookeeper
* @deprecated Master is an implementation detail for HBaseAdmin.
* Deprecated in HBase 0.94
*/
@Deprecated
public HMasterInterface getMaster()
throws MasterNotRunningException, ZooKeeperConnectionException {
return this.connection.getMaster();
}
/** @return - true if the master server is running
* @throws ZooKeeperConnectionException
* @throws MasterNotRunningException */
public boolean isMasterRunning()
throws MasterNotRunningException, ZooKeeperConnectionException {
return this.connection.isMasterRunning();
}
/**
* @param tableName Table to check.
* @return True if table exists already.
* @throws IOException
*/
public boolean tableExists(final String tableName)
throws IOException {
boolean b = false;
CatalogTracker ct = getCatalogTracker();
try {
b = MetaReader.tableExists(ct, tableName);
} finally {
cleanupCatalogTracker(ct);
}
return b;
}
/**
* @param tableName Table to check.
* @return True if table exists already.
* @throws IOException
*/
public boolean tableExists(final byte [] tableName)
throws IOException {
return tableExists(Bytes.toString(tableName));
}
/**
* List all the userspace tables. In other words, scan the META table.
*
* If we wanted this to be really fast, we could implement a special
* catalog table that just contains table names and their descriptors.
* Right now, it only exists as part of the META table's region info.
*
* @return - returns an array of HTableDescriptors
* @throws IOException if a remote or network exception occurs
*/
public HTableDescriptor[] listTables() throws IOException {
return this.connection.listTables();
}
/**
* List all the userspace tables matching the given pattern.
*
* @param pattern The compiled regular expression to match against
* @return - returns an array of HTableDescriptors
* @throws IOException if a remote or network exception occurs
* @see #listTables()
*/
public HTableDescriptor[] listTables(Pattern pattern) throws IOException {
List<HTableDescriptor> matched = new LinkedList<HTableDescriptor>();
HTableDescriptor[] tables = listTables();
for (HTableDescriptor table : tables) {
if (pattern.matcher(table.getNameAsString()).matches()) {
matched.add(table);
}
}
return matched.toArray(new HTableDescriptor[matched.size()]);
}
/**
* List all the userspace tables matching the given regular expression.
*
* @param regex The regular expression to match against
* @return - returns an array of HTableDescriptors
* @throws IOException if a remote or network exception occurs
* @see #listTables(java.util.regex.Pattern)
*/
public HTableDescriptor[] listTables(String regex) throws IOException {
return listTables(Pattern.compile(regex));
}
/**
* Method for getting the tableDescriptor
* @param tableName as a byte []
* @return the tableDescriptor
* @throws TableNotFoundException
* @throws IOException if a remote or network exception occurs
*/
public HTableDescriptor getTableDescriptor(final byte [] tableName)
throws TableNotFoundException, IOException {
return this.connection.getHTableDescriptor(tableName);
}
private long getPauseTime(int tries) {
int triesCount = tries;
if (triesCount >= HConstants.RETRY_BACKOFF.length) {
triesCount = HConstants.RETRY_BACKOFF.length - 1;
}
return this.pause * HConstants.RETRY_BACKOFF[triesCount];
}
/**
* Creates a new table.
* Synchronous operation.
*
* @param desc table descriptor for table
*
* @throws IllegalArgumentException if the table name is reserved
* @throws MasterNotRunningException if master is not running
* @throws TableExistsException if table already exists (If concurrent
* threads, the table may have been created between test-for-existence
* and attempt-at-creation).
* @throws IOException if a remote or network exception occurs
*/
public void createTable(HTableDescriptor desc)
throws IOException {
createTable(desc, null);
}
/**
* Creates a new table with the specified number of regions. The start key
* specified will become the end key of the first region of the table, and
* the end key specified will become the start key of the last region of the
* table (the first region has a null start key and the last region has a
* null end key).
*
* BigInteger math will be used to divide the key range specified into
* enough segments to make the required number of total regions.
*
* Synchronous operation.
*
* @param desc table descriptor for table
* @param startKey beginning of key range
* @param endKey end of key range
* @param numRegions the total number of regions to create
*
* @throws IllegalArgumentException if the table name is reserved
* @throws MasterNotRunningException if master is not running
* @throws TableExistsException if table already exists (If concurrent
* threads, the table may have been created between test-for-existence
* and attempt-at-creation).
* @throws IOException
*/
public void createTable(HTableDescriptor desc, byte [] startKey,
byte [] endKey, int numRegions)
throws IOException {
HTableDescriptor.isLegalTableName(desc.getName());
if(numRegions < 3) {
throw new IllegalArgumentException("Must create at least three regions");
} else if(Bytes.compareTo(startKey, endKey) >= 0) {
throw new IllegalArgumentException("Start key must be smaller than end key");
}
if (numRegions == 3) {
createTable(desc, new byte[][] { startKey, endKey });
return;
}
byte [][] splitKeys = Bytes.split(startKey, endKey, numRegions - 3);
if(splitKeys == null || splitKeys.length != numRegions - 1) {
throw new IllegalArgumentException("Unable to split key range into enough regions");
}
createTable(desc, splitKeys);
}
/**
* Creates a new table with an initial set of empty regions defined by the
* specified split keys. The total number of regions created will be the
* number of split keys plus one. Synchronous operation.
* Note : Avoid passing empty split key.
*
* @param desc table descriptor for table
* @param splitKeys array of split keys for the initial regions of the table
*
* @throws IllegalArgumentException if the table name is reserved, if the split keys
* are repeated and if the split key has empty byte array.
* @throws MasterNotRunningException if master is not running
* @throws TableExistsException if table already exists (If concurrent
* threads, the table may have been created between test-for-existence
* and attempt-at-creation).
* @throws IOException
*/
public void createTable(final HTableDescriptor desc, byte [][] splitKeys)
throws IOException {
HTableDescriptor.isLegalTableName(desc.getName());
try {
createTableAsync(desc, splitKeys);
} catch (SocketTimeoutException ste) {
LOG.warn("Creating " + desc.getNameAsString() + " took too long", ste);
}
int numRegs = splitKeys == null ? 1 : splitKeys.length + 1;
int prevRegCount = 0;
boolean doneWithMetaScan = false;
MetaScannerVisitorBaseWithTableName userTableVisitor = null;
MetaScannerVisitorBaseWithTableName indexTableVisitor = null;
boolean indexedHTD = isIndexedHTD(desc);
for (int tries = 0; tries < this.numRetries * this.retryLongerMultiplier; ++tries) {
AtomicInteger actualRegCount = null;
// Wait for new table to come on-line
if (userTableVisitor == null) {
userTableVisitor = new MetaScannerVisitorBaseWithTableName(desc.getNameAsString());
}
actualRegCount = userTableVisitor.getActualRgnCnt();
actualRegCount.set(0);
MetaScanner.metaScan(conf, userTableVisitor, desc.getName());
if (actualRegCount.get() != numRegs) {
if (tries == this.numRetries * this.retryLongerMultiplier - 1) {
throw new RegionOfflineException("Only " + actualRegCount.get() + " of " + numRegs
+ " regions are online; retries exhausted.");
}
try { // Sleep
Thread.sleep(getPauseTime(tries));
} catch (InterruptedException e) {
throw new InterruptedIOException("Interrupted when opening" + " regions; "
+ actualRegCount.get() + " of " + numRegs + " regions processed so far");
}
if (actualRegCount.get() > prevRegCount) { // Making progress
prevRegCount = actualRegCount.get();
tries = -1;
}
} else {
if (indexedHTD) {
String indexTableName = desc.getNameAsString() + INDEX_TABLE_SUFFIX;
if (indexTableVisitor == null) {
indexTableVisitor = new MetaScannerVisitorBaseWithTableName(indexTableName);
}
actualRegCount = indexTableVisitor.getActualRgnCnt();
actualRegCount.set(0);
MetaScanner.metaScan(conf, indexTableVisitor, Bytes.toBytes(indexTableName));
if (actualRegCount.get() != numRegs) {
if (tries == this.numRetries * this.retryLongerMultiplier - 1) {
throw new RegionOfflineException("Only " + actualRegCount.get() + " of " + numRegs
+ " regions are online; retries exhausted.");
}
try { // Sleep
Thread.sleep(getPauseTime(tries));
} catch (InterruptedException e) {
throw new InterruptedIOException("Interrupted when opening" + " regions; "
+ actualRegCount.get() + " of " + numRegs + " regions processed so far");
}
if (actualRegCount.get() > prevRegCount) { // Making progress
prevRegCount = actualRegCount.get();
tries = -1;
}
} else if (isTableEnabled(indexTableName)) {
return;
}
} else if (isTableEnabled(desc.getName())) {
return;
}
}
}
throw new TableNotEnabledException(
"Retries exhausted while still waiting for table: "
+ desc.getNameAsString() + " to be enabled");
}
private boolean isIndexedHTD(final HTableDescriptor desc) {
try {
Class<?> IndexedHTDKlass = HTableDescriptor.class;
IndexedHTDKlass = Class.forName("org.apache.hadoop.hbase.index.IndexedHTableDescriptor");
return IndexedHTDKlass.isInstance(desc);
} catch (ClassNotFoundException e) {
// Nothing to do.
}
return false;
}
public class MetaScannerVisitorBaseWithTableName implements MetaScannerVisitor {
byte[] tableName = null;
AtomicInteger actualRegCount = new AtomicInteger(0);
MetaScannerVisitorBaseWithTableName(String tableName) {
this.tableName = Bytes.toBytes(tableName);
}
AtomicInteger getActualRgnCnt() {
return actualRegCount;
}
@Override
public void close() throws IOException {
}
@Override
public boolean processRow(Result rowResult) throws IOException {
HRegionInfo info =
Writables.getHRegionInfoOrNull(rowResult.getValue(HConstants.CATALOG_FAMILY,
HConstants.REGIONINFO_QUALIFIER));
// If regioninfo is null, skip this row
if (null == info) {
return true;
}
if (!(Bytes.equals(info.getTableName(), tableName))) {
return false;
}
String hostAndPort = null;
byte[] value = rowResult.getValue(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER);
// Make sure that regions are assigned to server
if (value != null && value.length > 0) {
hostAndPort = Bytes.toString(value);
}
if (!(info.isOffline() || info.isSplit()) && hostAndPort != null) {
actualRegCount.incrementAndGet();
}
return true;
}
}
/**
* Creates a new table but does not block and wait for it to come online.
* Asynchronous operation. To check if the table exists, use
* {@link: #isTableAvailable} -- it is not safe to create an HTable
* instance to this table before it is available.
* Note : Avoid passing empty split key.
* @param desc table descriptor for table
*
* @throws IllegalArgumentException Bad table name, if the split keys
* are repeated and if the split key has empty byte array.
* @throws MasterNotRunningException if master is not running
* @throws TableExistsException if table already exists (If concurrent
* threads, the table may have been created between test-for-existence
* and attempt-at-creation).
* @throws IOException
*/
public void createTableAsync(HTableDescriptor desc, byte [][] splitKeys)
throws IOException {
HTableDescriptor.isLegalTableName(desc.getName());
if(splitKeys != null && splitKeys.length > 0) {
Arrays.sort(splitKeys, Bytes.BYTES_COMPARATOR);
// Verify there are no duplicate split keys
byte [] lastKey = null;
for(byte [] splitKey : splitKeys) {
if (Bytes.compareTo(splitKey, HConstants.EMPTY_BYTE_ARRAY) == 0) {
throw new IllegalArgumentException(
"Empty split key must not be passed in the split keys.");
}
if(lastKey != null && Bytes.equals(splitKey, lastKey)) {
throw new IllegalArgumentException("All split keys must be unique, " +
"found duplicate: " + Bytes.toStringBinary(splitKey) +
", " + Bytes.toStringBinary(lastKey));
}
lastKey = splitKey;
}
}
try {
getMaster().createTable(desc, splitKeys);
} catch (RemoteException e) {
throw e.unwrapRemoteException();
}
}
/**
* Deletes a table.
* Synchronous operation.
*
* @param tableName name of table to delete
* @throws IOException if a remote or network exception occurs
*/
public void deleteTable(final String tableName) throws IOException {
deleteTable(Bytes.toBytes(tableName));
}
/**
* Deletes a table.
* Synchronous operation.
*
* @param tableName name of table to delete
* @throws IOException if a remote or network exception occurs
*/
public void deleteTable(final byte [] tableName) throws IOException {
isMasterRunning();
HTableDescriptor.isLegalTableName(tableName);
HRegionLocation firstMetaServer = getFirstMetaServerForTable(tableName);
boolean tableExists = true;
try {
getMaster().deleteTable(tableName);
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
// Wait until all regions deleted
HRegionInterface server =
connection.getHRegionConnection(firstMetaServer.getHostname(), firstMetaServer.getPort());
for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
long scannerId = -1L;
try {
Scan scan = MetaReader.getScanForTableName(tableName);
scan.addColumn(HConstants.CATALOG_FAMILY,
HConstants.REGIONINFO_QUALIFIER);
scannerId = server.openScanner(
firstMetaServer.getRegionInfo().getRegionName(), scan);
// Get a batch at a time.
Result values = server.next(scannerId);
// let us wait until .META. table is updated and
// HMaster removes the table from its HTableDescriptors
if (values == null) {
tableExists = false;
HTableDescriptor[] htds = getMaster().getHTableDescriptors();
if (htds != null && htds.length > 0) {
for (HTableDescriptor htd: htds) {
if (Bytes.equals(tableName, htd.getName())) {
tableExists = true;
break;
}
}
}
if (!tableExists) {
break;
}
}
} catch (IOException ex) {
if(tries == numRetries - 1) { // no more tries left
if (ex instanceof RemoteException) {
ex = RemoteExceptionHandler.decodeRemoteException((RemoteException) ex);
}
throw ex;
}
} finally {
if (scannerId != -1L) {
try {
server.close(scannerId);
} catch (Exception ex) {
LOG.warn(ex);
}
}
}
try {
Thread.sleep(getPauseTime(tries));
} catch (InterruptedException e) {
// continue
}
}
if (tableExists) {
throw new IOException("Retries exhausted, it took too long to wait"+
" for the table " + Bytes.toString(tableName) + " to be deleted.");
}
// Delete cached information to prevent clients from using old locations
this.connection.clearRegionCache(tableName);
LOG.info("Deleted " + Bytes.toString(tableName));
}
/**
* Deletes tables matching the passed in pattern and wait on completion.
*
* Warning: Use this method carefully, there is no prompting and the effect is
* immediate. Consider using {@link #listTables(java.lang.String)} and
* {@link #deleteTable(byte[])}
*
* @param regex The regular expression to match table names against
* @return Table descriptors for tables that couldn't be deleted
* @throws IOException
* @see #deleteTables(java.util.regex.Pattern)
* @see #deleteTable(java.lang.String)
*/
public HTableDescriptor[] deleteTables(String regex) throws IOException {
return deleteTables(Pattern.compile(regex));
}
/**
* Delete tables matching the passed in pattern and wait on completion.
*
* Warning: Use this method carefully, there is no prompting and the effect is
* immediate. Consider using {@link #listTables(java.util.regex.Pattern) } and
* {@link #deleteTable(byte[])}
*
* @param pattern The pattern to match table names against
* @return Table descriptors for tables that couldn't be deleted
* @throws IOException
*/
public HTableDescriptor[] deleteTables(Pattern pattern) throws IOException {
List<HTableDescriptor> failed = new LinkedList<HTableDescriptor>();
for (HTableDescriptor table : listTables(pattern)) {
try {
deleteTable(table.getName());
} catch (IOException ex) {
LOG.info("Failed to delete table " + table.getNameAsString(), ex);
failed.add(table);
}
}
return failed.toArray(new HTableDescriptor[failed.size()]);
}
public void enableTable(final String tableName)
throws IOException {
enableTable(Bytes.toBytes(tableName));
}
/**
* Enable a table. May timeout. Use {@link #enableTableAsync(byte[])}
* and {@link #isTableEnabled(byte[])} instead.
* The table has to be in disabled state for it to be enabled.
* @param tableName name of the table
* @throws IOException if a remote or network exception occurs
* There could be couple types of IOException
* TableNotFoundException means the table doesn't exist.
* TableNotDisabledException means the table isn't in disabled state.
* @see #isTableEnabled(byte[])
* @see #disableTable(byte[])
* @see #enableTableAsync(byte[])
*/
public void enableTable(final byte [] tableName)
throws IOException {
enableTableAsync(tableName);
// Wait until all regions are enabled
waitUntilTableIsEnabled(tableName);
LOG.info("Enabled table " + Bytes.toString(tableName));
}
/**
* Wait for the table to be enabled and available
* If enabling the table exceeds the retry period, an exception is thrown.
* @param tableName name of the table
* @throws IOException if a remote or network exception occurs or
* table is not enabled after the retries period.
*/
private void waitUntilTableIsEnabled(final byte[] tableName) throws IOException {
boolean enabled = false;
long start = EnvironmentEdgeManager.currentTimeMillis();
for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
enabled = isTableEnabled(tableName) && isTableAvailable(tableName);
if (enabled) {
break;
}
long sleep = getPauseTime(tries);
if (LOG.isDebugEnabled()) {
LOG.debug("Sleeping= " + sleep + "ms, waiting for all regions to be " +
"enabled in " + Bytes.toString(tableName));
}
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// Do this conversion rather than let it out because do not want to
// change the method signature.
throw new IOException("Interrupted", e);
}
}
if (!enabled) {
long msec = EnvironmentEdgeManager.currentTimeMillis() - start;
throw new IOException("Table '" + Bytes.toString(tableName) +
"' not yet enabled, after " + msec + "ms.");
}
}
public void enableTableAsync(final String tableName)
throws IOException {
enableTableAsync(Bytes.toBytes(tableName));
}
/**
* Brings a table on-line (enables it). Method returns immediately though
* enable of table may take some time to complete, especially if the table
* is large (All regions are opened as part of enabling process). Check
* {@link #isTableEnabled(byte[])} to learn when table is fully online. If
* table is taking too long to online, check server logs.
* @param tableName
* @throws IOException
* @since 0.90.0
*/
public void enableTableAsync(final byte [] tableName)
throws IOException {
HTableDescriptor.isLegalTableName(tableName);
isMasterRunning();
try {
getMaster().enableTable(tableName);
} catch (RemoteException e) {
throw e.unwrapRemoteException();
}
LOG.info("Started enable of " + Bytes.toString(tableName));
}
/**
* Enable tables matching the passed in pattern and wait on completion.
*
* Warning: Use this method carefully, there is no prompting and the effect is
* immediate. Consider using {@link #listTables(java.lang.String)} and
* {@link #enableTable(byte[])}
*
* @param regex The regular expression to match table names against
* @throws IOException
* @see #enableTables(java.util.regex.Pattern)
* @see #enableTable(java.lang.String)
*/
public HTableDescriptor[] enableTables(String regex) throws IOException {
return enableTables(Pattern.compile(regex));
}
/**
* Enable tables matching the passed in pattern and wait on completion.
*
* Warning: Use this method carefully, there is no prompting and the effect is
* immediate. Consider using {@link #listTables(java.util.regex.Pattern) } and
* {@link #enableTable(byte[])}
*
* @param pattern The pattern to match table names against
* @throws IOException
*/
public HTableDescriptor[] enableTables(Pattern pattern) throws IOException {
List<HTableDescriptor> failed = new LinkedList<HTableDescriptor>();
for (HTableDescriptor table : listTables(pattern)) {
if (isTableDisabled(table.getName())) {
try {
enableTable(table.getName());
} catch (IOException ex) {
LOG.info("Failed to enable table " + table.getNameAsString(), ex);
failed.add(table);
}
}
}
return failed.toArray(new HTableDescriptor[failed.size()]);
}
public void disableTableAsync(final String tableName) throws IOException {
disableTableAsync(Bytes.toBytes(tableName));
}
/**
* Starts the disable of a table. If it is being served, the master
* will tell the servers to stop serving it. This method returns immediately.
* The disable of a table can take some time if the table is large (all
* regions are closed as part of table disable operation).
* Call {@link #isTableDisabled(byte[])} to check for when disable completes.
* If table is taking too long to online, check server logs.
* @param tableName name of table
* @throws IOException if a remote or network exception occurs
* @see #isTableDisabled(byte[])
* @see #isTableEnabled(byte[])
* @since 0.90.0
*/
public void disableTableAsync(final byte [] tableName) throws IOException {
HTableDescriptor.isLegalTableName(tableName);
isMasterRunning();
try {
getMaster().disableTable(tableName);
} catch (RemoteException e) {
throw e.unwrapRemoteException();
}
LOG.info("Started disable of " + Bytes.toString(tableName));
}
public void disableTable(final String tableName)
throws IOException {
disableTable(Bytes.toBytes(tableName));
}
/**
* Disable table and wait on completion. May timeout eventually. Use
* {@link #disableTableAsync(byte[])} and {@link #isTableDisabled(String)}
* instead.
* The table has to be in enabled state for it to be disabled.
* @param tableName
* @throws IOException
* There could be couple types of IOException
* TableNotFoundException means the table doesn't exist.
* TableNotEnabledException means the table isn't in enabled state.
*/
public void disableTable(final byte [] tableName)
throws IOException {
disableTableAsync(tableName);
// Wait until table is disabled
boolean disabled = false;
for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
disabled = isTableDisabled(tableName);
if (disabled) {
break;
}
long sleep = getPauseTime(tries);
if (LOG.isDebugEnabled()) {
LOG.debug("Sleeping= " + sleep + "ms, waiting for all regions to be " +
"disabled in " + Bytes.toString(tableName));
}
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
// Do this conversion rather than let it out because do not want to
// change the method signature.
Thread.currentThread().interrupt();
throw new IOException("Interrupted", e);
}
}
if (!disabled) {
throw new RegionException("Retries exhausted, it took too long to wait"+
" for the table " + Bytes.toString(tableName) + " to be disabled.");
}
LOG.info("Disabled " + Bytes.toString(tableName));
}
/**
* Disable tables matching the passed in pattern and wait on completion.
*
* Warning: Use this method carefully, there is no prompting and the effect is
* immediate. Consider using {@link #listTables(java.lang.String)} and
* {@link #disableTable(byte[])}
*
* @param regex The regular expression to match table names against
* @return Table descriptors for tables that couldn't be disabled
* @throws IOException
* @see #disableTables(java.util.regex.Pattern)
* @see #disableTable(java.lang.String)
*/
public HTableDescriptor[] disableTables(String regex) throws IOException {
return disableTables(Pattern.compile(regex));
}
/**
* Disable tables matching the passed in pattern and wait on completion.
*
* Warning: Use this method carefully, there is no prompting and the effect is
* immediate. Consider using {@link #listTables(java.util.regex.Pattern) } and
* {@link #disableTable(byte[])}
*
* @param pattern The pattern to match table names against
* @return Table descriptors for tables that couldn't be disabled
* @throws IOException
*/
public HTableDescriptor[] disableTables(Pattern pattern) throws IOException {
List<HTableDescriptor> failed = new LinkedList<HTableDescriptor>();
for (HTableDescriptor table : listTables(pattern)) {
if (isTableEnabled(table.getName())) {
try {
disableTable(table.getName());
} catch (IOException ex) {
LOG.info("Failed to disable table " + table.getNameAsString(), ex);
failed.add(table);
}
}
}
return failed.toArray(new HTableDescriptor[failed.size()]);
}
/**
* @param tableName name of table to check
* @return true if table is on-line
* @throws IOException if a remote or network exception occurs
*/
public boolean isTableEnabled(String tableName) throws IOException {
return isTableEnabled(Bytes.toBytes(tableName));
}
/**
* @param tableName name of table to check
* @return true if table is on-line
* @throws IOException if a remote or network exception occurs
*/
public boolean isTableEnabled(byte[] tableName) throws IOException {
if (!HTableDescriptor.isMetaTable(tableName)) {
HTableDescriptor.isLegalTableName(tableName);
}
if(!tableExists(tableName)){
throw new TableNotFoundException(Bytes.toString(tableName));
}
boolean indexEnabled = this.conf.getBoolean("hbase.use.secondary.index", false);
if (!indexEnabled) {
return connection.isTableEnabled(tableName);
} else {
boolean isTableEnabled = connection.isTableEnabled(tableName);
if (isTableEnabled && !isIndexTable(Bytes.toString(tableName))) {
String indexTableName = getIndexTableName(Bytes.toString(tableName));
if (connection.isTableAvailable(Bytes.toBytes(indexTableName))) {
return connection.isTableEnabled(Bytes.toBytes(indexTableName));
}
return true;
}
return isTableEnabled;
}
}
/**
* @param tableName name of table to check
* @return true if table is off-line
* @throws IOException if a remote or network exception occurs
*/
public boolean isTableDisabled(final String tableName) throws IOException {
return isTableDisabled(Bytes.toBytes(tableName));
}
public static boolean isIndexTable(String tableName) {
return tableName.endsWith(INDEX_TABLE_SUFFIX);
}
public static String getIndexTableName(String tableName) {
// TODO The suffix for the index table is fixed now. Do we allow to make this configurable?
// We can handle things in byte[] way?
return tableName + INDEX_TABLE_SUFFIX;
}
/**
* @param tableName name of table to check
* @return true if table is off-line
* @throws IOException if a remote or network exception occurs
*/
public boolean isTableDisabled(byte[] tableName) throws IOException {
if (!HTableDescriptor.isMetaTable(tableName)) {
HTableDescriptor.isLegalTableName(tableName);
}
boolean indexEnabled = this.conf.getBoolean("hbase.use.secondary.index", false);
if (!indexEnabled) {
return connection.isTableDisabled(tableName);
} else {
boolean isTableDisabled = connection.isTableDisabled(tableName);
if (isTableDisabled && !isIndexTable(Bytes.toString(tableName))) {
String indexTableName = getIndexTableName(Bytes.toString(tableName));
if (connection.isTableAvailable(Bytes.toBytes(indexTableName))) {
return connection.isTableDisabled(Bytes.toBytes(indexTableName));
}
return true;
}
return isTableDisabled;
}
}
/**
* @param tableName name of table to check
* @return true if all regions of the table are available
* @throws IOException if a remote or network exception occurs
*/
public boolean isTableAvailable(byte[] tableName) throws IOException {
return connection.isTableAvailable(tableName);
}
/**
* @param tableName name of table to check
* @return true if all regions of the table are available
* @throws IOException if a remote or network exception occurs
*/
public boolean isTableAvailable(String tableName) throws IOException {
return connection.isTableAvailable(Bytes.toBytes(tableName));
}
/**
* Get the status of alter command - indicates how many regions have received
* the updated schema Asynchronous operation.
*
* @param tableName
* name of the table to get the status of
* @return Pair indicating the number of regions updated Pair.getFirst() is the
* regions that are yet to be updated Pair.getSecond() is the total number
* of regions of the table
* @throws IOException
* if a remote or network exception occurs
*/
public Pair<Integer, Integer> getAlterStatus(final byte[] tableName)
throws IOException {
HTableDescriptor.isLegalTableName(tableName);
try {
return getMaster().getAlterStatus(tableName);
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Add a column to an existing table.
* Asynchronous operation.
*
* @param tableName name of the table to add column to
* @param column column descriptor of column to be added
* @throws IOException if a remote or network exception occurs
*/
public void addColumn(final String tableName, HColumnDescriptor column)
throws IOException {
addColumn(Bytes.toBytes(tableName), column);
}
/**
* Add a column to an existing table.
* Asynchronous operation.
*
* @param tableName name of the table to add column to
* @param column column descriptor of column to be added
* @throws IOException if a remote or network exception occurs
*/
public void addColumn(final byte [] tableName, HColumnDescriptor column)
throws IOException {
HTableDescriptor.isLegalTableName(tableName);
try {
getMaster().addColumn(tableName, column);
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Delete a column from a table.
* Asynchronous operation.
*
* @param tableName name of table
* @param columnName name of column to be deleted
* @throws IOException if a remote or network exception occurs
*/
public void deleteColumn(final String tableName, final String columnName)
throws IOException {
deleteColumn(Bytes.toBytes(tableName), Bytes.toBytes(columnName));
}
/**
* Delete a column from a table.
* Asynchronous operation.
*
* @param tableName name of table
* @param columnName name of column to be deleted
* @throws IOException if a remote or network exception occurs
*/
public void deleteColumn(final byte [] tableName, final byte [] columnName)
throws IOException {
try {
getMaster().deleteColumn(tableName, columnName);
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Modify an existing column family on a table.
* Asynchronous operation.
*
* @param tableName name of table
* @param descriptor new column descriptor to use
* @throws IOException if a remote or network exception occurs
*/
public void modifyColumn(final String tableName, HColumnDescriptor descriptor)
throws IOException {
modifyColumn(Bytes.toBytes(tableName), descriptor);
}
/**
* Modify an existing column family on a table.
* Asynchronous operation.
*
* @param tableName name of table
* @param descriptor new column descriptor to use
* @throws IOException if a remote or network exception occurs
*/
public void modifyColumn(final byte [] tableName, HColumnDescriptor descriptor)
throws IOException {
try {
getMaster().modifyColumn(tableName, descriptor);
} catch (RemoteException re) {
// Convert RE exceptions in here; client shouldn't have to deal with them,
// at least w/ the type of exceptions that come out of this method:
// TableNotFoundException, etc.
throw RemoteExceptionHandler.decodeRemoteException(re);
}
}
/**
* Close a region. For expert-admins. Runs close on the regionserver. The
* master will not be informed of the close.
* @param regionname region name to close
* @param serverName If supplied, we'll use this location rather than
* the one currently in <code>.META.</code>
* @throws IOException if a remote or network exception occurs
*/
public void closeRegion(final String regionname, final String serverName)
throws IOException {
closeRegion(Bytes.toBytes(regionname), serverName);
}
/**
* Close a region. For expert-admins Runs close on the regionserver. The
* master will not be informed of the close.
* @param regionname region name to close
* @param serverName The servername of the regionserver. If passed null we
* will use servername found in the .META. table. A server name
* is made of host, port and startcode. Here is an example:
* <code> host187.example.com,60020,1289493121758</code>
* @throws IOException if a remote or network exception occurs
*/
public void closeRegion(final byte [] regionname, final String serverName)
throws IOException {
CatalogTracker ct = getCatalogTracker();
try {
if (serverName != null) {
Pair<HRegionInfo, ServerName> pair = MetaReader.getRegion(ct, regionname);
if (pair == null || pair.getFirst() == null) {
throw new UnknownRegionException(Bytes.toStringBinary(regionname));
} else {
closeRegion(new ServerName(serverName), pair.getFirst());
}
} else {
Pair<HRegionInfo, ServerName> pair = MetaReader.getRegion(ct, regionname);
if (pair == null) {
throw new UnknownRegionException(Bytes.toStringBinary(regionname));
} else if (pair.getSecond() == null) {
throw new NoServerForRegionException(Bytes.toStringBinary(regionname));
} else {
closeRegion(pair.getSecond(), pair.getFirst());
}
}
} finally {
cleanupCatalogTracker(ct);
}
}
/**
* For expert-admins. Runs close on the regionserver. Closes a region based on
* the encoded region name. The region server name is mandatory. If the
* servername is provided then based on the online regions in the specified
* regionserver the specified region will be closed. The master will not be
* informed of the close. Note that the regionname is the encoded regionname.
*
* @param encodedRegionName
* The encoded region name; i.e. the hash that makes up the region
* name suffix: e.g. if regionname is
* <code>TestTable,0094429456,1289497600452.527db22f95c8a9e0116f0cc13c680396.</code>
* , then the encoded region name is:
* <code>527db22f95c8a9e0116f0cc13c680396</code>.
* @param serverName
* The servername of the regionserver. A server name is made of host,
* port and startcode. This is mandatory. Here is an example:
* <code> host187.example.com,60020,1289493121758</code>
* @return true if the region was closed, false if not.
* @throws IOException
* if a remote or network exception occurs
*/
public boolean closeRegionWithEncodedRegionName(final String encodedRegionName,
final String serverName) throws IOException {
byte[] encodedRegionNameInBytes = Bytes.toBytes(encodedRegionName);
if (null == serverName || ("").equals(serverName.trim())) {
throw new IllegalArgumentException(
"The servername cannot be null or empty.");
}
ServerName sn = new ServerName(serverName);
HRegionInterface rs = this.connection.getHRegionConnection(
sn.getHostname(), sn.getPort());
// Close the region without updating zk state.
boolean isRegionClosed = rs.closeRegion(encodedRegionNameInBytes, false);
if (false == isRegionClosed) {
LOG.error("Not able to close the region " + encodedRegionName + ".");
}
return isRegionClosed;
}
/**
* Close a region. For expert-admins Runs close on the regionserver. The
* master will not be informed of the close.
* @param sn
* @param hri
* @throws IOException
*/
public void closeRegion(final ServerName sn, final HRegionInfo hri)
throws IOException {
HRegionInterface rs =
this.connection.getHRegionConnection(sn.getHostname(), sn.getPort());
// Close the region without updating zk state.
rs.closeRegion(hri, false);
}
/**
* Flush a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to flush
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void flush(final String tableNameOrRegionName)
throws IOException, InterruptedException {
flush(Bytes.toBytes(tableNameOrRegionName));
}
/**
* Flush a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to flush
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void flush(final byte [] tableNameOrRegionName)
throws IOException, InterruptedException {
CatalogTracker ct = getCatalogTracker();
try {
Pair<HRegionInfo, ServerName> regionServerPair
= getRegion(tableNameOrRegionName, ct);
if (regionServerPair != null) {
if (regionServerPair.getSecond() == null) {
throw new NoServerForRegionException(Bytes.toStringBinary(tableNameOrRegionName));
} else {
flush(regionServerPair.getSecond(), regionServerPair.getFirst());
}
} else {
final String tableName = tableNameString(tableNameOrRegionName, ct);
List<Pair<HRegionInfo, ServerName>> pairs =
MetaReader.getTableRegionsAndLocations(ct,
tableName);
for (Pair<HRegionInfo, ServerName> pair: pairs) {
if (pair.getFirst().isOffline()) continue;
if (pair.getSecond() == null) continue;
try {
flush(pair.getSecond(), pair.getFirst());
} catch (NotServingRegionException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Trying to flush " + pair.getFirst() + ": " +
StringUtils.stringifyException(e));
}
}
}
}
} finally {
cleanupCatalogTracker(ct);
}
}
private void flush(final ServerName sn, final HRegionInfo hri)
throws IOException {
HRegionInterface rs =
this.connection.getHRegionConnection(sn.getHostname(), sn.getPort());
rs.flushRegion(hri);
}
/**
* Compact a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to compact
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void compact(final String tableNameOrRegionName)
throws IOException, InterruptedException {
compact(Bytes.toBytes(tableNameOrRegionName));
}
/**
* Compact a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to compact
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void compact(final byte [] tableNameOrRegionName)
throws IOException, InterruptedException {
compact(tableNameOrRegionName, null, false);
}
/**
* Compact a column family within a table or region.
* Asynchronous operation.
*
* @param tableOrRegionName table or region to compact
* @param columnFamily column family within a table or region
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void compact(String tableOrRegionName, String columnFamily)
throws IOException, InterruptedException {
compact(Bytes.toBytes(tableOrRegionName), Bytes.toBytes(columnFamily));
}
/**
* Compact a column family within a table or region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to compact
* @param columnFamily column family within a table or region
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void compact(final byte [] tableNameOrRegionName, final byte[] columnFamily)
throws IOException, InterruptedException {
compact(tableNameOrRegionName, columnFamily, false);
}
/**
* Major compact a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to major compact
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void majorCompact(final String tableNameOrRegionName)
throws IOException, InterruptedException {
majorCompact(Bytes.toBytes(tableNameOrRegionName));
}
/**
* Major compact a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to major compact
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void majorCompact(final byte [] tableNameOrRegionName)
throws IOException, InterruptedException {
compact(tableNameOrRegionName, null, true);
}
/**
* Major compact a column family within a table or region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to major compact
* @param columnFamily column family within a table or region
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void majorCompact(final String tableNameOrRegionName,
final String columnFamily) throws IOException, InterruptedException {
majorCompact(Bytes.toBytes(tableNameOrRegionName),
Bytes.toBytes(columnFamily));
}
/**
* Major compact a column family within a table or region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to major compact
* @param columnFamily column family within a table or region
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void majorCompact(final byte [] tableNameOrRegionName,
final byte[] columnFamily) throws IOException, InterruptedException {
compact(tableNameOrRegionName, columnFamily, true);
}
/**
* Compact a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to compact
* @param columnFamily column family within a table or region
* @param major True if we are to do a major compaction.
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
private void compact(final byte [] tableNameOrRegionName,
final byte[] columnFamily, final boolean major)
throws IOException, InterruptedException {
CatalogTracker ct = getCatalogTracker();
try {
Pair<HRegionInfo, ServerName> regionServerPair
= getRegion(tableNameOrRegionName, ct);
if (regionServerPair != null) {
if (regionServerPair.getSecond() == null) {
throw new NoServerForRegionException(Bytes.toStringBinary(tableNameOrRegionName));
} else {
compact(regionServerPair.getSecond(), regionServerPair.getFirst(), major, columnFamily);
}
} else {
final String tableName = tableNameString(tableNameOrRegionName, ct);
List<Pair<HRegionInfo, ServerName>> pairs =
MetaReader.getTableRegionsAndLocations(ct,
tableName);
for (Pair<HRegionInfo, ServerName> pair: pairs) {
if (pair.getFirst().isOffline()) continue;
if (pair.getSecond() == null) continue;
try {
compact(pair.getSecond(), pair.getFirst(), major, columnFamily);
} catch (NotServingRegionException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Trying to" + (major ? " major" : "") + " compact " +
pair.getFirst() + ": " +
StringUtils.stringifyException(e));
}
}
}
}
} finally {
cleanupCatalogTracker(ct);
}
}
private void compact(final ServerName sn, final HRegionInfo hri,
final boolean major, final byte [] family)
throws IOException {
HRegionInterface rs =
this.connection.getHRegionConnection(sn.getHostname(), sn.getPort());
if (family != null) {
try {
rs.compactRegion(hri, major, family);
} catch (IOException ioe) {
String notFoundMsg = "java.lang.NoSuchMethodException: org.apache.hadoop.hbase.ipc.HRegionInterface."
+ "compactRegion(org.apache.hadoop.hbase.HRegionInfo, boolean, [B)";
if (ioe.getMessage().contains(notFoundMsg)) {
throw new IOException("per-column family compaction not supported on this version "
+ "of the HBase server. You may still compact at the table or region level by "
+ "omitting the column family name. Alternatively, you can upgrade the HBase server");
}
throw ioe;
}
} else {
rs.compactRegion(hri, major);
}
}
/**
* Move the region <code>r</code> to <code>dest</code>.
* @param encodedRegionName The encoded region name; i.e. the hash that makes
* up the region name suffix: e.g. if regionname is
* <code>TestTable,0094429456,1289497600452.527db22f95c8a9e0116f0cc13c680396.</code>,
* then the encoded region name is: <code>527db22f95c8a9e0116f0cc13c680396</code>.
* @param destServerName The servername of the destination regionserver. If
* passed the empty byte array we'll assign to a random server. A server name
* is made of host, port and startcode. Here is an example:
* <code> host187.example.com,60020,1289493121758</code>
* @throws UnknownRegionException Thrown if we can't find a region named
* <code>encodedRegionName</code>
* @throws ZooKeeperConnectionException
* @throws MasterNotRunningException
*/
public void move(final byte [] encodedRegionName, final byte [] destServerName)
throws UnknownRegionException, MasterNotRunningException, ZooKeeperConnectionException {
getMaster().move(encodedRegionName, destServerName);
}
/**
* @param regionName
* Region name to assign.
* @throws MasterNotRunningException
* @throws ZooKeeperConnectionException
* @throws IOException
*/
public void assign(final byte[] regionName) throws MasterNotRunningException,
ZooKeeperConnectionException, IOException {
getMaster().assign(regionName);
}
/**
* Unassign a region from current hosting regionserver. Region will then be
* assigned to a regionserver chosen at random. Region could be reassigned
* back to the same server. Use {@link #move(byte[], byte[])} if you want
* to control the region movement.
* @param regionName Region to unassign. Will clear any existing RegionPlan
* if one found.
* @param force If true, force unassign (Will remove region from
* regions-in-transition too if present. If results in double assignment
* use hbck -fix to resolve. To be used by experts).
* @throws MasterNotRunningException
* @throws ZooKeeperConnectionException
* @throws IOException
*/
public void unassign(final byte [] regionName, final boolean force)
throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
getMaster().unassign(regionName, force);
}
/**
* Turn the load balancer on or off.
* @param b If true, enable balancer. If false, disable balancer.
* @return Previous balancer value
* @deprecated use setBalancerRunning(boolean, boolean) instead
*/
@Deprecated
public boolean balanceSwitch(final boolean b)
throws MasterNotRunningException, ZooKeeperConnectionException {
return getMaster().balanceSwitch(b);
}
/**
* Turn the load balancer on or off.
* @param on If true, enable balancer. If false, disable balancer.
* @param synchronous If true, it waits until current balance() call, if outstanding, to return.
* @return Previous balancer value
*/
public boolean setBalancerRunning(final boolean on, final boolean synchronous)
throws MasterNotRunningException, ZooKeeperConnectionException {
if (synchronous && synchronousBalanceSwitchSupported) {
try {
return getMaster().synchronousBalanceSwitch(on);
} catch (UndeclaredThrowableException ute) {
String error = ute.getCause().getMessage();
if (error != null && error.matches(
"(?s).+NoSuchMethodException:.+synchronousBalanceSwitch.+")) {
LOG.info("HMaster doesn't support synchronousBalanceSwitch");
synchronousBalanceSwitchSupported = false;
} else {
throw ute;
}
}
}
return balanceSwitch(on);
}
/**
* Invoke the balancer. Will run the balancer and if regions to move, it will
* go ahead and do the reassignments. Can NOT run for various reasons. Check
* logs.
* @return True if balancer ran, false otherwise.
*/
public boolean balancer()
throws MasterNotRunningException, ZooKeeperConnectionException {
return getMaster().balance();
}
/**
* Split a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table or region to split
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void split(final String tableNameOrRegionName)
throws IOException, InterruptedException {
split(Bytes.toBytes(tableNameOrRegionName));
}
/**
* Split a table or an individual region. Implicitly finds an optimal split
* point. Asynchronous operation.
*
* @param tableNameOrRegionName table to region to split
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
*/
public void split(final byte [] tableNameOrRegionName)
throws IOException, InterruptedException {
split(tableNameOrRegionName, null);
}
public void split(final String tableNameOrRegionName,
final String splitPoint) throws IOException, InterruptedException {
split(Bytes.toBytes(tableNameOrRegionName), Bytes.toBytes(splitPoint));
}
/**
* Split a table or an individual region.
* Asynchronous operation.
*
* @param tableNameOrRegionName table to region to split
* @param splitPoint the explicit position to split on
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException interrupt exception occurred
*/
public void split(final byte [] tableNameOrRegionName,
final byte [] splitPoint) throws IOException, InterruptedException {
CatalogTracker ct = getCatalogTracker();
try {
Pair<HRegionInfo, ServerName> regionServerPair
= getRegion(tableNameOrRegionName, ct);
if (regionServerPair != null) {
if (regionServerPair.getSecond() == null) {
throw new NoServerForRegionException(Bytes.toStringBinary(tableNameOrRegionName));
} else {
split(regionServerPair.getSecond(), regionServerPair.getFirst(), splitPoint);
}
} else {
final String tableName = tableNameString(tableNameOrRegionName, ct);
List<Pair<HRegionInfo, ServerName>> pairs =
MetaReader.getTableRegionsAndLocations(ct,
tableName);
for (Pair<HRegionInfo, ServerName> pair: pairs) {
// May not be a server for a particular row
if (pair.getSecond() == null) continue;
HRegionInfo r = pair.getFirst();
// check for parents
if (r.isSplitParent()) continue;
// if a split point given, only split that particular region
if (splitPoint != null && !r.containsRow(splitPoint)) continue;
// call out to region server to do split now
split(pair.getSecond(), pair.getFirst(), splitPoint);
}
}
} finally {
cleanupCatalogTracker(ct);
}
}
private void split(final ServerName sn, final HRegionInfo hri,
byte[] splitPoint) throws IOException {
HRegionInterface rs =
this.connection.getHRegionConnection(sn.getHostname(), sn.getPort());
rs.splitRegion(hri, splitPoint);
}
/**
* Modify an existing table, more IRB friendly version.
* Asynchronous operation. This means that it may be a while before your
* schema change is updated across all of the table.
*
* @param tableName name of table.
* @param htd modified description of the table
* @throws IOException if a remote or network exception occurs
*/
public void modifyTable(final byte [] tableName, HTableDescriptor htd)
throws IOException {
try {
getMaster().modifyTable(tableName, htd);
} catch (RemoteException re) {
// Convert RE exceptions in here; client shouldn't have to deal with them,
// at least w/ the type of exceptions that come out of this method:
// TableNotFoundException, etc.
throw RemoteExceptionHandler.decodeRemoteException(re);
}
}
/**
* @param tableNameOrRegionName Name of a table or name of a region.
* @param ct A {@link CatalogTracker} instance (caller of this method usually has one).
* @return a pair of HRegionInfo and ServerName if <code>tableNameOrRegionName</code> is
* a verified region name (we call {@link MetaReader#getRegion( CatalogTracker, byte[])}
* else null.
* Throw an exception if <code>tableNameOrRegionName</code> is null.
* @throws IOException
*/
Pair<HRegionInfo, ServerName> getRegion(final byte[] tableNameOrRegionName,
final CatalogTracker ct) throws IOException {
if (tableNameOrRegionName == null) {
throw new IllegalArgumentException("Pass a table name or region name");
}
Pair<HRegionInfo, ServerName> pair = MetaReader.getRegion(ct, tableNameOrRegionName);
if (pair == null) {
final AtomicReference<Pair<HRegionInfo, ServerName>> result =
new AtomicReference<Pair<HRegionInfo, ServerName>>(null);
final String encodedName = Bytes.toString(tableNameOrRegionName);
MetaScannerVisitor visitor = new MetaScannerVisitorBase() {
@Override
public boolean processRow(Result data) throws IOException {
if (data == null || data.size() <= 0) {
return true;
}
HRegionInfo info = MetaReader.parseHRegionInfoFromCatalogResult(
data, HConstants.REGIONINFO_QUALIFIER);
if (info == null) {
LOG.warn("No serialized HRegionInfo in " + data);
return true;
}
if (!encodedName.equals(info.getEncodedName())) return true;
ServerName sn = MetaReader.getServerNameFromCatalogResult(data);
result.set(new Pair<HRegionInfo, ServerName>(info, sn));
return false; // found the region, stop
}
};
MetaScanner.metaScan(conf, visitor);
pair = result.get();
}
return pair;
}
/**
* Convert the table name byte array into a table name string and check if table
* exists or not.
* @param tableNameBytes Name of a table.
* @param ct A {@link #CatalogTracker} instance (caller of this method usually has one).
* @return tableName in string form.
* @throws IOException if a remote or network exception occurs.
* @throws TableNotFoundException if table does not exist.
*/
private String tableNameString(final byte[] tableNameBytes, CatalogTracker ct)
throws IOException {
String tableNameString = Bytes.toString(tableNameBytes);
if (!MetaReader.tableExists(ct, tableNameString)) {
throw new TableNotFoundException(tableNameString);
}
return tableNameString;
}
/**
* Shuts down the HBase cluster
* @throws IOException if a remote or network exception occurs
*/
public synchronized void shutdown() throws IOException {
isMasterRunning();
try {
getMaster().shutdown();
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Shuts down the current HBase master only.
* Does not shutdown the cluster.
* @see #shutdown()
* @throws IOException if a remote or network exception occurs
*/
public synchronized void stopMaster() throws IOException {
isMasterRunning();
try {
getMaster().stopMaster();
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Stop the designated regionserver
* @param hostnamePort Hostname and port delimited by a <code>:</code> as in
* <code>example.org:1234</code>
* @throws IOException if a remote or network exception occurs
*/
public synchronized void stopRegionServer(final String hostnamePort)
throws IOException {
String hostname = Addressing.parseHostname(hostnamePort);
int port = Addressing.parsePort(hostnamePort);
HRegionInterface rs =
this.connection.getHRegionConnection(hostname, port);
rs.stop("Called by admin client " + this.connection.toString());
}
/**
* @return cluster status
* @throws IOException if a remote or network exception occurs
*/
public ClusterStatus getClusterStatus() throws IOException {
return getMaster().getClusterStatus();
}
private HRegionLocation getFirstMetaServerForTable(final byte [] tableName)
throws IOException {
return connection.locateRegion(HConstants.META_TABLE_NAME,
HRegionInfo.createRegionName(tableName, null, HConstants.NINES, false));
}
/**
* @return Configuration used by the instance.
*/
public Configuration getConfiguration() {
return this.conf;
}
/**
* Check to see if HBase is running. Throw an exception if not.
*
* @param conf system configuration
* @throws MasterNotRunningException if the master is not running
* @throws ZooKeeperConnectionException if unable to connect to zookeeper
*/
public static void checkHBaseAvailable(Configuration conf)
throws MasterNotRunningException, ZooKeeperConnectionException {
Configuration copyOfConf = HBaseConfiguration.create(conf);
copyOfConf.setInt("hbase.client.retries.number", 1);
HBaseAdmin admin = new HBaseAdmin(copyOfConf);
try {
admin.close();
} catch (IOException ioe) {
admin.LOG.info("Failed to close connection", ioe);
}
}
/**
* get the regions of a given table.
*
* @param tableName the name of the table
* @return Ordered list of {@link HRegionInfo}.
* @throws IOException
*/
public List<HRegionInfo> getTableRegions(final byte[] tableName)
throws IOException {
CatalogTracker ct = getCatalogTracker();
List<HRegionInfo> Regions = null;
try {
Regions = MetaReader.getTableRegions(ct, tableName, true);
} finally {
cleanupCatalogTracker(ct);
}
return Regions;
}
public void close() throws IOException {
if (this.connection != null) {
this.connection.close();
}
}
/**
* Get tableDescriptors
* @param tableNames List of table names
* @return HTD[] the tableDescriptor
* @throws IOException if a remote or network exception occurs
*/
public HTableDescriptor[] getTableDescriptors(List<String> tableNames)
throws IOException {
return this.connection.getHTableDescriptors(tableNames);
}
/**
* Roll the log writer. That is, start writing log messages to a new file.
*
* @param serverName
* The servername of the regionserver. A server name is made of host,
* port and startcode. This is mandatory. Here is an example:
* <code> host187.example.com,60020,1289493121758</code>
* @return If lots of logs, flush the returned regions so next time through
* we can clean logs. Returns null if nothing to flush. Names are actual
* region names as returned by {@link HRegionInfo#getEncodedName()}
* @throws IOException if a remote or network exception occurs
* @throws FailedLogCloseException
*/
public synchronized byte[][] rollHLogWriter(String serverName)
throws IOException, FailedLogCloseException {
ServerName sn = new ServerName(serverName);
HRegionInterface rs = this.connection.getHRegionConnection(
sn.getHostname(), sn.getPort());
return rs.rollHLogWriter();
}
public String[] getMasterCoprocessors() {
try {
return getClusterStatus().getMasterCoprocessors();
} catch (IOException e) {
LOG.error("Could not getClusterStatus()",e);
return null;
}
}
/**
* Get the current compaction state of a table or region.
* It could be in a major compaction, a minor compaction, both, or none.
*
* @param tableNameOrRegionName table or region to major compact
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
* @return the current compaction state
*/
public CompactionState getCompactionState(final String tableNameOrRegionName)
throws IOException, InterruptedException {
return getCompactionState(Bytes.toBytes(tableNameOrRegionName));
}
/**
* Get the current compaction state of a table or region.
* It could be in a major compaction, a minor compaction, both, or none.
*
* @param tableNameOrRegionName table or region to major compact
* @throws IOException if a remote or network exception occurs
* @throws InterruptedException
* @return the current compaction state
*/
public CompactionState getCompactionState(final byte [] tableNameOrRegionName)
throws IOException, InterruptedException {
CompactionState state = CompactionState.NONE;
CatalogTracker ct = getCatalogTracker();
try {
Pair<HRegionInfo, ServerName> regionServerPair
= getRegion(tableNameOrRegionName, ct);
if (regionServerPair != null) {
if (regionServerPair.getSecond() == null) {
throw new NoServerForRegionException(Bytes.toStringBinary(tableNameOrRegionName));
} else {
ServerName sn = regionServerPair.getSecond();
HRegionInterface rs =
this.connection.getHRegionConnection(sn.getHostname(), sn.getPort());
return CompactionState.valueOf(
rs.getCompactionState(regionServerPair.getFirst().getRegionName()));
}
} else {
final String tableName = tableNameString(tableNameOrRegionName, ct);
List<Pair<HRegionInfo, ServerName>> pairs =
MetaReader.getTableRegionsAndLocations(ct, tableName);
for (Pair<HRegionInfo, ServerName> pair: pairs) {
if (pair.getFirst().isOffline()) continue;
if (pair.getSecond() == null) continue;
try {
ServerName sn = pair.getSecond();
HRegionInterface rs =
this.connection.getHRegionConnection(sn.getHostname(), sn.getPort());
switch (CompactionState.valueOf(
rs.getCompactionState(pair.getFirst().getRegionName()))) {
case MAJOR_AND_MINOR:
return CompactionState.MAJOR_AND_MINOR;
case MAJOR:
if (state == CompactionState.MINOR) {
return CompactionState.MAJOR_AND_MINOR;
}
state = CompactionState.MAJOR;
break;
case MINOR:
if (state == CompactionState.MAJOR) {
return CompactionState.MAJOR_AND_MINOR;
}
state = CompactionState.MINOR;
break;
case NONE:
default: // nothing, continue
}
} catch (NotServingRegionException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Trying to get compaction state of " +
pair.getFirst() + ": " +
StringUtils.stringifyException(e));
}
}
}
}
} finally {
cleanupCatalogTracker(ct);
}
return state;
}
/**
* Creates and returns a proxy to the CoprocessorProtocol instance running in the
* master.
*
* @param protocol The class or interface defining the remote protocol
* @return A CoprocessorProtocol instance
*/
public <T extends CoprocessorProtocol> T coprocessorProxy(
Class<T> protocol) {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[]{protocol},
new MasterExecRPCInvoker(conf,
connection,
protocol));
}
/**
* Create a timestamp consistent snapshot for the given table.
* <p>
* Snapshots are considered unique based on <b>the name of the snapshot</b>. Attempts to take a
* snapshot with the same name (even a different type or with different parameters) will fail with
* a {@link SnapshotCreationException} indicating the duplicate naming.
* <p>
* Snapshot names follow the same naming constraints as tables in HBase. See
* {@link HTableDescriptor#isLegalTableName(byte[])}.
* @param snapshotName name of the snapshot to be created
* @param tableName name of the table for which snapshot is created
* @throws IOException if a remote or network exception occurs
* @throws SnapshotCreationException if snapshot creation failed
* @throws IllegalArgumentException if the snapshot request is formatted incorrectly
*/
public void snapshot(final String snapshotName, final String tableName) throws IOException,
SnapshotCreationException, IllegalArgumentException {
snapshot(snapshotName, tableName, SnapshotDescription.Type.FLUSH);
}
/**
* Take a snapshot for the given table. If the table is enabled, a FLUSH-type snapshot will be
* taken. If the table is disabled, an offline snapshot is taken.
* <p>
* Snapshots are considered unique based on <b>the name of the snapshot</b>. Attempts to take a
* snapshot with the same name (even a different type or with different parameters) will fail with
* a {@link SnapshotCreationException} indicating the duplicate naming.
* <p>
* Snapshot names follow the same naming constraints as tables in HBase. See
* {@link HTableDescriptor#isLegalTableName(byte[])}.
* @param snapshotName name of the snapshot to be created
* @param tableName name of the table for which snapshot is created
* @throws IOException if a remote or network exception occurs
* @throws SnapshotCreationException if snapshot creation failed
* @throws IllegalArgumentException if the snapshot request is formatted incorrectly
*/
public void snapshot(final byte[] snapshotName, final byte[] tableName) throws IOException,
SnapshotCreationException, IllegalArgumentException {
snapshot(Bytes.toString(snapshotName), Bytes.toString(tableName));
}
/**
* Create typed snapshot of the table.
* <p>
* Snapshots are considered unique based on <b>the name of the snapshot</b>. Attempts to take a
* snapshot with the same name (even a different type or with different parameters) will fail with
* a {@link SnapshotCreationException} indicating the duplicate naming.
* <p>
* Snapshot names follow the same naming constraints as tables in HBase. See
* {@link HTableDescriptor#isLegalTableName(byte[])}.
* <p>
* @param snapshotName name to give the snapshot on the filesystem. Must be unique from all other
* snapshots stored on the cluster
* @param tableName name of the table to snapshot
* @param type type of snapshot to take
* @throws IOException we fail to reach the master
* @throws SnapshotCreationException if snapshot creation failed
* @throws IllegalArgumentException if the snapshot request is formatted incorrectly
*/
public void snapshot(final String snapshotName, final String tableName,
SnapshotDescription.Type type) throws IOException, SnapshotCreationException,
IllegalArgumentException {
SnapshotDescription.Builder builder = SnapshotDescription.newBuilder();
builder.setTable(tableName);
builder.setName(snapshotName);
builder.setType(type);
snapshot(builder.build());
}
/**
* Take a snapshot and wait for the server to complete that snapshot (blocking).
* <p>
* Only a single snapshot should be taken at a time for an instance of HBase, or results may be
* undefined (you can tell multiple HBase clusters to snapshot at the same time, but only one at a
* time for a single cluster).
* <p>
* Snapshots are considered unique based on <b>the name of the snapshot</b>. Attempts to take a
* snapshot with the same name (even a different type or with different parameters) will fail with
* a {@link SnapshotCreationException} indicating the duplicate naming.
* <p>
* Snapshot names follow the same naming constraints as tables in HBase. See
* {@link HTableDescriptor#isLegalTableName(byte[])}.
* <p>
* You should probably use {@link #snapshot(String, String)} or {@link #snapshot(byte[], byte[])}
* unless you are sure about the type of snapshot that you want to take.
* @param snapshot snapshot to take
* @throws IOException or we lose contact with the master.
* @throws SnapshotCreationException if snapshot failed to be taken
* @throws IllegalArgumentException if the snapshot request is formatted incorrectly
*/
public void snapshot(SnapshotDescription snapshot) throws IOException, SnapshotCreationException,
IllegalArgumentException {
HSnapshotDescription snapshotWritable = new HSnapshotDescription(snapshot);
try {
// actually take the snapshot
long max = takeSnapshotAsync(snapshot);
long start = EnvironmentEdgeManager.currentTimeMillis();
long maxPauseTime = max / this.numRetries;
boolean done = false;
int tries = 0;
LOG.debug("Waiting a max of " + max + " ms for snapshot '" +
SnapshotDescriptionUtils.toString(snapshot) + "' to complete. (max " +
maxPauseTime + " ms per retry)");
while (tries == 0 || (EnvironmentEdgeManager.currentTimeMillis() - start) < max && !done) {
try {
// sleep a backoff <= pauseTime amount
long sleep = getPauseTime(tries++);
sleep = sleep > maxPauseTime ? maxPauseTime : sleep;
LOG.debug("(#" + tries + ") Sleeping: " + sleep +
"ms while waiting for snapshot completion.");
Thread.sleep(sleep);
} catch (InterruptedException e) {
LOG.debug("Interrupted while waiting for snapshot " + snapshot + " to complete");
Thread.currentThread().interrupt();
}
LOG.debug("Getting current status of snapshot from master...");
done = getMaster().isSnapshotDone(snapshotWritable);
}
if (!done) {
throw new SnapshotCreationException("Snapshot '" + snapshot.getName()
+ "' wasn't completed in expectedTime:" + max + " ms", snapshot);
}
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Take a snapshot without waiting for the server to complete that snapshot (asynchronous)
* <p>
* Only a single snapshot should be taken at a time, or results may be undefined.
* @param snapshot snapshot to take
* @return the max time in millis to wait for the snapshot
* @throws IOException if the snapshot did not succeed or we lose contact with the master.
* @throws SnapshotCreationException if snapshot creation failed
* @throws IllegalArgumentException if the snapshot request is formatted incorrectly
*/
public long takeSnapshotAsync(SnapshotDescription snapshot) throws IOException,
SnapshotCreationException {
SnapshotDescriptionUtils.assertSnapshotRequestIsValid(snapshot);
HSnapshotDescription snapshotWritable = new HSnapshotDescription(snapshot);
return getMaster().snapshot(snapshotWritable);
}
/**
* Check the current state of the passed snapshot.
* <p>
* There are three possible states:
* <ol>
* <li>running - returns <tt>false</tt></li>
* <li>finished - returns <tt>true</tt></li>
* <li>finished with error - throws the exception that caused the snapshot to fail</li>
* </ol>
* <p>
* The cluster only knows about the most recent snapshot. Therefore, if another snapshot has been
* run/started since the snapshot your are checking, you will recieve an
* {@link UnknownSnapshotException}.
* @param snapshot description of the snapshot to check
* @return <tt>true</tt> if the snapshot is completed, <tt>false</tt> if the snapshot is still
* running
* @throws IOException if we have a network issue
* @throws HBaseSnapshotException if the snapshot failed
* @throws UnknownSnapshotException if the requested snapshot is unknown
*/
public boolean isSnapshotFinished(final SnapshotDescription snapshot)
throws IOException, HBaseSnapshotException, UnknownSnapshotException {
try {
return getMaster().isSnapshotDone(new HSnapshotDescription(snapshot));
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Restore the specified snapshot on the original table. (The table must be disabled)
* Before restoring the table, a new snapshot with the current table state is created.
* In case of failure, the table will be rolled back to the its original state.
*
* @param snapshotName name of the snapshot to restore
* @throws IOException if a remote or network exception occurs
* @throws RestoreSnapshotException if snapshot failed to be restored
* @throws IllegalArgumentException if the restore request is formatted incorrectly
*/
public void restoreSnapshot(final byte[] snapshotName)
throws IOException, RestoreSnapshotException {
restoreSnapshot(Bytes.toString(snapshotName));
}
/**
* Restore the specified snapshot on the original table. (The table must be disabled)
* Before restoring the table, a new snapshot with the current table state is created.
* In case of failure, the table will be rolled back to its original state.
*
* @param snapshotName name of the snapshot to restore
* @throws IOException if a remote or network exception occurs
* @throws RestoreSnapshotException if snapshot failed to be restored
* @throws IllegalArgumentException if the restore request is formatted incorrectly
*/
public void restoreSnapshot(final String snapshotName)
throws IOException, RestoreSnapshotException {
String rollbackSnapshot = snapshotName + "-" + EnvironmentEdgeManager.currentTimeMillis();
String tableName = null;
for (SnapshotDescription snapshotInfo: listSnapshots()) {
if (snapshotInfo.getName().equals(snapshotName)) {
tableName = snapshotInfo.getTable();
break;
}
}
if (tableName == null) {
throw new RestoreSnapshotException(
"Unable to find the table name for snapshot=" + snapshotName);
}
// Take a snapshot of the current state
snapshot(rollbackSnapshot, tableName);
// Restore snapshot
try {
internalRestoreSnapshot(snapshotName, tableName);
} catch (IOException e) {
// Try to rollback
try {
String msg = "Restore snapshot=" + snapshotName +
" failed. Rollback to snapshot=" + rollbackSnapshot + " succeeded.";
LOG.error(msg, e);
internalRestoreSnapshot(rollbackSnapshot, tableName);
throw new RestoreSnapshotException(msg, e);
} catch (IOException ex) {
String msg = "Failed to restore and rollback to snapshot=" + rollbackSnapshot;
LOG.error(msg, ex);
throw new RestoreSnapshotException(msg, ex);
}
}
}
/**
* Create a new table by cloning the snapshot content.
*
* @param snapshotName name of the snapshot to be cloned
* @param tableName name of the table where the snapshot will be restored
* @throws IOException if a remote or network exception occurs
* @throws TableExistsException if table to be created already exists
* @throws RestoreSnapshotException if snapshot failed to be cloned
* @throws IllegalArgumentException if the specified table has not a valid name
*/
public void cloneSnapshot(final byte[] snapshotName, final byte[] tableName)
throws IOException, TableExistsException, RestoreSnapshotException, InterruptedException {
cloneSnapshot(Bytes.toString(snapshotName), Bytes.toString(tableName));
}
/**
* Create a new table by cloning the snapshot content.
*
* @param snapshotName name of the snapshot to be cloned
* @param tableName name of the table where the snapshot will be restored
* @throws IOException if a remote or network exception occurs
* @throws TableExistsException if table to be created already exists
* @throws RestoreSnapshotException if snapshot failed to be cloned
* @throws IllegalArgumentException if the specified table has not a valid name
*/
public void cloneSnapshot(final String snapshotName, final String tableName)
throws IOException, TableExistsException, RestoreSnapshotException, InterruptedException {
if (tableExists(tableName)) {
throw new TableExistsException("Table '" + tableName + " already exists");
}
internalRestoreSnapshot(snapshotName, tableName);
waitUntilTableIsEnabled(Bytes.toBytes(tableName));
}
/**
* Execute Restore/Clone snapshot and wait for the server to complete (blocking).
* To check if the cloned table exists, use {@link #isTableAvailable} -- it is not safe to
* create an HTable instance to this table before it is available.
* @param snapshot snapshot to restore
* @param tableName table name to restore the snapshot on
* @throws IOException if a remote or network exception occurs
* @throws RestoreSnapshotException if snapshot failed to be restored
* @throws IllegalArgumentException if the restore request is formatted incorrectly
*/
private void internalRestoreSnapshot(final String snapshotName, final String tableName)
throws IOException, RestoreSnapshotException {
HSnapshotDescription snapshot = new HSnapshotDescription(
SnapshotDescription.newBuilder().setName(snapshotName).setTable(tableName).build());
try {
// actually restore the snapshot
getMaster().restoreSnapshot(snapshot);
final long maxPauseTime = 5000;
boolean done = false;
int tries = 0;
while (!done) {
try {
// sleep a backoff <= pauseTime amount
long sleep = getPauseTime(tries++);
sleep = sleep > maxPauseTime ? maxPauseTime : sleep;
LOG.debug(tries + ") Sleeping: " + sleep + " ms while we wait for snapshot restore to complete.");
Thread.sleep(sleep);
} catch (InterruptedException e) {
LOG.debug("Interrupted while waiting for snapshot " + snapshot + " restore to complete");
Thread.currentThread().interrupt();
}
LOG.debug("Getting current status of snapshot restore from master...");
done = getMaster().isRestoreSnapshotDone(snapshot);
}
if (!done) {
throw new RestoreSnapshotException("Snapshot '" + snapshot.getName() + "' wasn't restored.");
}
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* List completed snapshots.
* @return a list of snapshot descriptors for completed snapshots
* @throws IOException if a network error occurs
*/
public List<SnapshotDescription> listSnapshots() throws IOException {
List<SnapshotDescription> snapshots = new LinkedList<SnapshotDescription>();
try {
for (HSnapshotDescription snapshot: getMaster().getCompletedSnapshots()) {
snapshots.add(snapshot.getProto());
}
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
return snapshots;
}
/**
* Delete an existing snapshot.
* @param snapshotName name of the snapshot
* @throws IOException if a remote or network exception occurs
*/
public void deleteSnapshot(final byte[] snapshotName) throws IOException {
// make sure the snapshot is possibly valid
HTableDescriptor.isLegalTableName(snapshotName);
// do the delete
SnapshotDescription snapshot = SnapshotDescription.newBuilder()
.setName(Bytes.toString(snapshotName)).build();
try {
getMaster().deleteSnapshot(new HSnapshotDescription(snapshot));
} catch (RemoteException e) {
throw RemoteExceptionHandler.decodeRemoteException(e);
}
}
/**
* Delete an existing snapshot.
* @param snapshotName name of the snapshot
* @throws IOException if a remote or network exception occurs
*/
public void deleteSnapshot(final String snapshotName) throws IOException {
deleteSnapshot(Bytes.toBytes(snapshotName));
}
/**
* Forcefully sets the table state as DISABLED in ZK
* @param tableName
*/
public void setDisableTable(String tableName) throws IOException {
setTableState(tableName, "DISABLED");
}
/**
* Forcefully sets the table state as ENABLED in ZK
* @param tablename
*/
public void setEnableTable(String tableName) throws IOException {
setTableState(tableName, "ENABLED");
}
private void setTableState(String tableName, String state) throws IOException {
try {
getMaster().setTableState(tableName, state);
} catch (RemoteException e) {
throw e.unwrapRemoteException();
}
}
}