Package com.alibaba.wasp.client

Source Code of com.alibaba.wasp.client.WaspAdmin$MasterAdminCallable

/**
* Copyright 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 com.alibaba.wasp.client;

import com.alibaba.wasp.*;
import com.alibaba.wasp.conf.WaspConfiguration;
import com.alibaba.wasp.fserver.AdminProtocol;
import com.alibaba.wasp.master.FMasterAdminProtocol;
import com.alibaba.wasp.meta.FTable;
import com.alibaba.wasp.meta.Field;
import com.alibaba.wasp.meta.Index;
import com.alibaba.wasp.protobuf.ProtobufUtil;
import com.alibaba.wasp.protobuf.RequestConverter;
import com.alibaba.wasp.protobuf.generated.FServerAdminProtos.CloseEncodedEntityGroupRequest;
import com.alibaba.wasp.protobuf.generated.FServerAdminProtos.CloseEncodedEntityGroupResponse;
import com.alibaba.wasp.protobuf.generated.FServerAdminProtos.StopServerRequest;
import com.alibaba.wasp.protobuf.generated.MasterAdminProtos;
import com.alibaba.wasp.protobuf.generated.MasterMonitorProtos.GetClusterStatusRequest;
import com.alibaba.wasp.protobuf.generated.MasterMonitorProtos.GetSchemaAlterStatusRequest;
import com.alibaba.wasp.protobuf.generated.MasterMonitorProtos.GetSchemaAlterStatusResponse;
import com.alibaba.wasp.zookeeper.ZooKeeperWatcher;
import com.google.protobuf.ServiceException;
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.util.Addressing;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.zookeeper.KeeperException;

import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;

public class WaspAdmin implements Abortable, Closeable {
  private static final Log LOG = LogFactory.getLog(WaspAdmin.class);

  // We use the implementation class rather then the interface because we
  // need the package protected functions to get the connection to master
  private FConnection 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... Multiply by this factor when
  // want to wait a long time.
  private final int retryLongerMultiplier;
  private boolean aborted;

  /**
   * Constructor. See {@link #WaspAdmin(FConnection connection)}
   *
   * @param c
   *          Configuration object. Copied internally.
   */
  public WaspAdmin(Configuration c) throws MasterNotRunningException,
      ZooKeeperConnectionException {
    // Will not leak connections, as the new implementation of the constructor
    // does not throw exceptions anymore.
    this(FConnectionManager.getConnection(new Configuration(c)));
  }

  /**
   * Constructor for externally managed FConnections. The connection to master
   * will be created when required by admin functions.
   *
   * @param connection
   *          The FConnection instance to use
   * @throws com.alibaba.wasp.MasterNotRunningException
   *           , ZooKeeperConnectionException are not thrown anymore but kept
   *           into the interface for backward api compatibility
   */
  public WaspAdmin(FConnection connection) throws MasterNotRunningException,
      ZooKeeperConnectionException {
    this.conf = connection.getConfiguration();
    this.connection = connection;
    this.pause = this.conf.getLong("wasp.client.pause", 1000);
    this.numRetries = this.conf.getInt("wasp.client.retries.number", 10);
    this.retryLongerMultiplier = this.conf.getInt(
        "wasp.client.retries.longer.multiplier", 10);
  }

  @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 FConnection used by this object. */
  public FConnection getConnection() {
    return connection;
  }

  /**
   * @return - true if the master server is running. Throws an exception
   *         otherwise.
   * @throws com.alibaba.wasp.ZooKeeperConnectionException
   * @throws com.alibaba.wasp.MasterNotRunningException
   */
  public boolean isMasterRunning() throws MasterNotRunningException,
      ZooKeeperConnectionException, MasterNotRunningException {
    return connection.isMasterRunning();
  }

  /**
   * @param tableName
   *          Table to check.
   * @return True if table exists already.
   * @throws java.io.IOException
   */
  public boolean tableExists(final String tableName) throws IOException {
    return tableExists(Bytes.toBytes(tableName));
  }

  /**
   * @param tableName
   *          Table to check.
   * @return True if table exists already.
   * @throws java.io.IOException
   */
  public boolean tableExists(final byte[] tableName) throws IOException {
    FTable ftable = getTableDescriptor(tableName);
    return ftable == null ? false : true;
  }

  /**
   * List all the userspace tables. In other words, scan the FMETA table.
   *
   * @return - returns an array of Table
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public FTable[] listTables() throws IOException {
    return this.connection.listTables();
  }

  /**
   * List all the userspace tables. In other words, scan the FMETA table.
   *
   * @return - returns an array of Table
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public Index[] listIndexes(final String tableName) throws IOException {
    FTable ftable = getTableDescriptor(Bytes.toBytes(tableName));
    LinkedHashMap<String, Index> indexMap = ftable.getIndex();
    Collection<Index> indexes = indexMap.values();
    return indexes.toArray(new Index[0]);
  }

  public String describeIndex(String tableName, String indexName)
      throws IOException {
    FTable table = getTableDescriptor(Bytes.toBytes(tableName));
    LinkedHashMap<String, Index> indexMap = table.getIndex();
    Index index = indexMap.get(indexName);
    if (index == null) {
      return "";
    }

    StringBuilder builder = new StringBuilder();
    builder.append("+----------------------+----------+-------+\n");
    builder.append("|              INDEX_KEYS                 |\n");
    builder.append("+----------------------+----------+-------+\n");
    builder.append("| Field                | Type     | ORDER |\n");
    builder.append("+----------------------+----------+-------+\n");
    String line = "| {0} | {1} | {2} |";
    LinkedHashMap<String, Field> indexKeys = index.getIndexKeys();
    Map<String, Field> storings = index.getStoring();
    Set<String> desc = index.getDesc();

    for (Field field : indexKeys.values()) {
      String fieldname = field.getName();
      String s0 = fieldname
          + (fieldname.length() < 20 ? getGivenBlanks(20 - fieldname.length())
              : "");
      String type = field.getType().toString();
      String s1 = type
          + (type.length() < 8 ? getGivenBlanks(8 - type.length()) : "");
      String s2 = desc.contains(fieldname) ? "desc " : "asc  ";
      builder.append(MessageFormat.format(line, s0, s1, s2));
      builder.append("\n");
    }
    builder.append("+----------------------+----------+-------+\n");
    builder.append("|               STORINGS                  |\n");
    builder.append("+----------------------+----------+-------+\n");
    builder.append("| Field                | Type     | ORDER |\n");
    builder.append("+----------------------+----------+-------+\n");
    for (Field field : storings.values()) {
      String fieldname = field.getName();
      String s0 = fieldname
          + (fieldname.length() < 15 ? getGivenBlanks(15 - fieldname.length())
              : "");
      String type = field.getType().toString();
      String s1 = type
          + (type.length() < 8 ? getGivenBlanks(8 - type.length()) : "");
      String s2 = desc.contains(fieldname) ? "desc " : "asc  ";
      builder.append(MessageFormat.format(line, s0, s1, s2));
      builder.append("\n");
    }
    builder.append("+----------------------+----------+-------+\n");
    return builder.toString();
  }

  public String describeTable(String tableName) throws IOException {
    FTable table = getTableDescriptor(Bytes.toBytes(tableName));
    String parentTableName = table.getParentName() == null ? "ROOT" : table.getParentName();
    StringBuilder builder = new StringBuilder();
    builder.append("+-------------------------------------------------------------+\n");
    builder.append("|                       Parent Table                          |\n");
    builder.append("+-------------------------------------------------------------+\n");
    builder.append("| ").append(parentTableName).append(getGivenBlanks(60 - parentTableName.length())).append("|\n");
    builder.append("+---------------------------+----------+----------+-----+-----+\n");
    builder.append("| Field                     | Type     | REQUIRED | Key | EGK |\n");
    builder.append("+---------------------------+----------+----------+-----+-----+\n");
    String line = "| {0} | {1} | {2} | {3} | {4} |";
    LinkedHashMap<String, Field> priKeys = table.getPrimaryKeys();
    Field egKey = table.getEntityGroupKey();
    for (Field field : table.getColumns().values()) {
      String fieldname = field.getName();
      String s0 = fieldname
          + (fieldname.length() < 25 ? getGivenBlanks(25 - fieldname.length())
              : "");
      String type = field.getType().toString();
      String s1 = type
          + (type.length() < 8 ? getGivenBlanks(8 - type.length()) : "");
      String nullAble = field.getKeyWord().toString();
      String s2 = nullAble
          + (nullAble.length() < 8 ? getGivenBlanks(8 - nullAble.length()) : "");
      String s3 = priKeys.get(fieldname) != null ? "PRI" : "   ";
      String s4 = fieldname.equals(egKey.getName()) ? "EGK" : "   ";
      builder.append(MessageFormat.format(line, s0, s1, s2, s3, s4));
      builder.append("\n");
    }
    builder.append("+---------------------------+----------+----------+-----+-----+\n");
    return builder.toString();
  }

  private String getGivenBlanks(int size) {
    StringBuilder builder = new StringBuilder(size);

    for (int i = 0; i < size; i++) {
      builder.append(" ");
    }

    return builder.toString();
  }

  /**
   * List all the userspace tables matching the given pattern.
   *
   * @param pattern
   *          The compiled regular expression to match against
   * @return - returns an array of Table
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   * @see #listTables()
   */
  public FTable[] listTables(Pattern pattern) throws IOException {
    List<FTable> matched = new LinkedList<FTable>();
    FTable[] tables = listTables();
    for (FTable table : tables) {
      if (pattern.matcher(table.getTableName()).matches()) {
        matched.add(table);
      }
    }
    return matched.toArray(new FTable[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 Table
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   * @see #listTables(java.util.regex.Pattern)
   */
  public FTable[] listTables(String regex) throws IOException {
    return listTables(Pattern.compile(regex));
  }

  /**
   * Method for getting the tableDescriptor
   *
   * @param tableName
   *          as a byte []
   * @return the tableDescriptor
   * @throws com.alibaba.wasp.TableNotFoundException
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public FTable getTableDescriptor(final byte[] tableName) throws IOException {
    return this.connection.getFTableDescriptor(tableName);
  }

  private long getPauseTime(int tries) {
    int triesCount = tries;
    if (triesCount >= FConstants.RETRY_BACKOFF.length) {
      triesCount = FConstants.RETRY_BACKOFF.length - 1;
    }
    return this.pause * FConstants.RETRY_BACKOFF[triesCount];
  }

  /**
   * Creates a new table. Synchronous operation.
   *
   * @param desc
   *          table descriptor for table
   *
   * @throws IllegalArgumentException
   *           if the table name is reserved
   * @throws com.alibaba.wasp.MasterNotRunningException
   *           if master is not running
   * @throws com.alibaba.wasp.TableExistsException
   *           if table already exists (If concurrent threads, the table may
   *           have been created between test-for-existence and
   *           attempt-at-creation).
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public void createTable(FTable desc) throws IOException {
    createTable(desc, null);
  }

  /**
   * Creates a new table with the specified number of entityGroups. The start
   * key specified will become the end key of the first entityGroup of the
   * table, and the end key specified will become the start key of the last
   * entityGroup of the table (the first entityGroup has a null start key and
   * the last entityGroup 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 entityGroups.
   *
   * Synchronous operation.
   *
   * @param desc
   *          table descriptor for table
   * @param startKey
   *          beginning of key range
   * @param endKey
   *          end of key range
   * @param numEntityGroups
   *          the total number of entityGroups to create
   *
   * @throws IllegalArgumentException
   *           if the table name is reserved
   * @throws com.alibaba.wasp.MasterNotRunningException
   *           if master is not running
   * @throws com.alibaba.wasp.TableExistsException
   *           if table already exists (If concurrent threads, the table may
   *           have been created between test-for-existence and
   *           attempt-at-creation).
   * @throws java.io.IOException
   */
  public void createTable(FTable desc, byte[] startKey, byte[] endKey,
      int numEntityGroups) throws IOException {
    FTable.isLegalTableName(desc.getTableName());
    if (numEntityGroups < 3) {
      throw new IllegalArgumentException(
          "Must create at least three entityGroups");
    } else if (Bytes.compareTo(startKey, endKey) >= 0) {
      throw new IllegalArgumentException(
          "Start key must be smaller than end key");
    }
    byte[][] splitKeys = Bytes.split(startKey, endKey, numEntityGroups - 3);
    if (splitKeys == null || splitKeys.length != numEntityGroups - 1) {
      throw new IllegalArgumentException(
          "Unable to split key range into enough entityGroups");
    }
    createTable(desc, splitKeys);
  }

  /**
   * Creates a new table with an initial set of empty entityGroups defined by
   * the specified split keys. The total number of entityGroups 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 entityGroups 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 com.alibaba.wasp.MasterNotRunningException
   *           if master is not running
   * @throws com.alibaba.wasp.TableExistsException
   *           if table already exists (If concurrent threads, the table may
   *           have been created between test-for-existence and
   *           attempt-at-creation).
   * @throws java.io.IOException
   */
  public void createTable(final FTable desc, byte[][] splitKeys)
      throws IOException {
    FTable.isLegalTableName(desc.getTableName());
    try {
      createTableAsync(desc, splitKeys);
    } catch (SocketTimeoutException ste) {
      LOG.warn("Creating " + desc.getTableName() + " took too long", ste);
    }
    final byte[] tableNameBytes = Bytes.toBytes(desc.getTableName());
    int numRegs = splitKeys == null ? 1 : splitKeys.length + 1;
    int prevRegCount = 0;
    boolean doneWithMetaScan = false;
    for (int tries = 0; tries < this.numRetries * this.retryLongerMultiplier; ++tries) {
      if (!doneWithMetaScan) {
        // Wait for new table to come on-line
        MasterAdminProtos.FetchEntityGroupSizeResponse res = execute(new MasterAdminCallable<MasterAdminProtos.FetchEntityGroupSizeResponse>() {
          @Override
          public MasterAdminProtos.FetchEntityGroupSizeResponse call() throws ServiceException {
            MasterAdminProtos.FetchEntityGroupSizeRequest request = RequestConverter
                .buildFetchEntityGroupSizeRequest(tableNameBytes);
            return masterAdmin.fetchEntityGroupSize(null, request);
          }
        });
        int actualEgCount = res.getEgSize();
        if (actualEgCount != numRegs && desc.isRootTable()) {
          if (tries == this.numRetries * this.retryLongerMultiplier - 1) {
            throw new EntityGroupOfflineException("Only " + actualEgCount
                + " of " + numRegs
                + " entityGroups are online; retries exhausted.");
          }
          try { // Sleep
            Thread.sleep(getPauseTime(tries));
          } catch (InterruptedException e) {
            throw new InterruptedIOException("Interrupted when opening"
                + " entityGroups; " + actualEgCount + " of " + numRegs
                + " entityGroups processed so far");
          }
          if (actualEgCount > prevRegCount) { // Making progress
            prevRegCount = actualEgCount;
            tries = -1;
          }
        } else {
          doneWithMetaScan = true;
          tries = -1;
        }
      } else if (isTableEnabled(desc.getTableName())) {
        return;
      } else {
        try { // Sleep
          Thread.sleep(getPauseTime(tries));
        } catch (InterruptedException e) {
          throw new InterruptedIOException("Interrupted when waiting"
              + " for table to be enabled; meta scan was done");
        }
      }
    }
    throw new TableNotEnabledException(
        "Retries exhausted while still waiting for table: "
            + desc.getTableName() + " to be enabled");
  }

  /**
   * 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 com.alibaba.wasp.MasterNotRunningException
   *           if master is not running
   * @throws com.alibaba.wasp.TableExistsException
   *           if table already exists (If concurrent threads, the table may
   *           have been created between test-for-existence and
   *           attempt-at-creation).
   * @throws java.io.IOException
   */
  public void createTableAsync(final FTable desc, final byte[][] splitKeys)
      throws IOException {
    FTable.isLegalTableName(desc.getTableName());
    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, FConstants.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;
      }
    }

    execute(new MasterAdminCallable<MasterAdminProtos.CreateTableResponse>() {
      @Override
      public MasterAdminProtos.CreateTableResponse call() throws ServiceException {
        MasterAdminProtos.CreateTableRequest request = RequestConverter.buildCreateTableRequest(
            desc, splitKeys);
        return masterAdmin.createTable(null, request);
      }
    });
  }

  /**
   * Create a index for given table.
   *
   * @param index
   *          index descriptor for index
   * @throws java.io.IOException
   */
  public void createIndex(final Index index) throws IOException {
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        MasterAdminProtos.CreateIndexRequest request = RequestConverter
            .buildCreateIndexRequest(index);
        masterAdmin.createIndex(null, request);
        return null;
      }
    });
  }

  /**
   * Drop a index for given indexName.
   *
   * @param indexName
   *          index descriptor for index
   * @throws java.io.IOException
   */
  public void deleteIndex(final String tableName, final String indexName)
      throws IOException {
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        MasterAdminProtos.DropIndexRequest request = RequestConverter.buildDropIndexRequest(
            tableName, indexName);
        masterAdmin.deleteIndex(null, request);
        return null;
      }
    });
  }

  /**
   * Deletes a table. Synchronous operation.
   *
   * @param tableName
   *          name of table to delete
   * @throws java.io.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 java.io.IOException
   *           if a remote or network exception occurs
   */
  public void deleteTable(final byte[] tableName) throws IOException {
    FTable.isLegalTableName(Bytes.toString(tableName));
    boolean tableExists = true;

    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        MasterAdminProtos.DeleteTableRequest req = RequestConverter
            .buildDeleteTableRequest(tableName);
        masterAdmin.deleteTable(null, req);
        return null;
      }
    });

    // Wait until all entityGroups deleted
    for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
      try {
        FTable fTable = this.getTableDescriptor(tableName);
        // let us wait until .FMETA. table is updated and
        tableExists = fTable != null;
        if (!tableExists) {
          break;
        }
      } catch (IOException ex) {
        if (tries == numRetries - 1) { // no more tries left
          if (ex instanceof RemoteException) {
            throw ((RemoteException) ex).unwrapRemoteException();
          }
          throw ex;
        }
      }
      try {
        Thread.sleep(getPauseTime(tries));
      } catch (InterruptedException e) {
        throw new IOException(e);
      }
    }

    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.clearEntityGroupCache(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(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 java.io.IOException
   * @see #deleteTables(java.util.regex.Pattern)
   * @see #deleteTable(String)
   */
  public FTable[] 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 java.io.IOException
   */
  public FTable[] deleteTables(Pattern pattern) throws IOException {
    List<FTable> failed = new LinkedList<FTable>();
    for (FTable table : listTables(pattern)) {
      try {
        deleteTable(table.getTableName());
      } catch (IOException ex) {
        LOG.info("Failed to delete table " + table.getTableName(), ex);
        failed.add(table);
      }
    }
    return failed.toArray(new FTable[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 java.io.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 entityGroups are enabled
    boolean enabled = false;
    for (int tries = 0; tries < (this.numRetries * this.retryLongerMultiplier); tries++) {
      enabled = isTableEnabled(tableName);
      if (enabled) {
        break;
      }
      long sleep = getPauseTime(tries);
      if (LOG.isDebugEnabled()) {
        LOG.debug("Sleeping= " + sleep
            + "ms, waiting for all entityGroups 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) {
      throw new IOException("Unable to enable table "
          + Bytes.toString(tableName));
    }
    LOG.info("Enabled table " + Bytes.toString(tableName));
  }

  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 entityGroups 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 java.io.IOException
   * @since 0.90.0
   */
  public void enableTableAsync(final byte[] tableName) throws IOException {
    FTable.isLegalTableName(Bytes.toString(tableName));
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        LOG.info("Started enable of " + Bytes.toString(tableName));
        MasterAdminProtos.EnableTableRequest req = RequestConverter
            .buildEnableTableRequest(tableName);
        masterAdmin.enableTable(null, req);
        return null;
      }
    });
  }

  /**
   * 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(String)} and
   * {@link #enableTable(byte[])}
   *
   * @param regex
   *          The regular expression to match table names against
   * @throws java.io.IOException
   * @see #enableTables(java.util.regex.Pattern)
   * @see #enableTable(String)
   */
  public FTable[] 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 java.io.IOException
   */
  public FTable[] enableTables(Pattern pattern) throws IOException {
    List<FTable> failed = new LinkedList<FTable>();
    for (FTable table : listTables(pattern)) {
      if (isTableDisabled(table.getTableName())) {
        try {
          enableTable(table.getTableName());
        } catch (IOException ex) {
          LOG.info("Failed to enable table " + table.getTableName(), ex);
          failed.add(table);
        }
      }
    }
    return failed.toArray(new FTable[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
   * entityGroups 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 java.io.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 {
    FTable.isLegalTableName(Bytes.toString(tableName));
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        LOG.info("Started disable of " + Bytes.toString(tableName));
        MasterAdminProtos.DisableTableRequest req = RequestConverter
            .buildMasterDisableTableRequest(tableName);
        masterAdmin.disableTable(null, req);
        return null;
      }
    });
  }

  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 java.io.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 entityGroups 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 EntityGroupException(
          "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(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 java.io.IOException
   * @see #disableTables(java.util.regex.Pattern)
   * @see #disableTable(String)
   */
  public FTable[] 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 java.io.IOException
   */
  public FTable[] disableTables(Pattern pattern) throws IOException {
    List<FTable> failed = new LinkedList<FTable>();
    for (FTable table : listTables(pattern)) {
      if (isTableEnabled(table.getTableName())) {
        try {
          disableTable(table.getTableName());
        } catch (IOException ex) {
          LOG.info("Failed to disable table " + table.getTableName(), ex);
          failed.add(table);
        }
      }
    }
    return failed.toArray(new FTable[failed.size()]);
  }

  /**
   * @param tableName
   *          name of table to check
   * @return true if table is on-line
   * @throws java.io.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 java.io.IOException
   *           if a remote or network exception occurs
   */
  public boolean isTableEnabled(byte[] tableName) throws IOException {
    return connection.isTableEnabled(tableName);
  }

  /**
   * @param tableName
   *          name of table to check
   * @return true if table is off-line
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public boolean isTableDisabled(final String tableName) throws IOException {
    return isTableDisabled(Bytes.toBytes(tableName));
  }

  /**
   * @param tableName
   *          name of table to check
   * @return true if table is off-line
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public boolean isTableDisabled(byte[] tableName) throws IOException {
    return connection.isTableDisabled(tableName);
  }

  /**
   * @param tableName
   *          name of table to check
   * @return true if all entityGroups of the table are available
   * @throws java.io.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 entityGroups of the table are available
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public boolean isTableAvailable(String tableName) throws IOException {
    return connection.isTableAvailable(Bytes.toBytes(tableName));
  }

  /**
   * @param tableName
   *          name of table to check
   * @return true if the table is locked
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public boolean isTableLocked(byte[] tableName) throws IOException {
    return connection.isTableLocked(tableName);
  }

  public boolean isTableLocked(String tableName) throws IOException {
    return isTableLocked(Bytes.toBytes(tableName));
  }

  public void unlockTable(final String tableName) throws IOException {
    unlockTable(Bytes.toBytes(tableName));
  }

  /**
   * Unlock the table, use it carefully. For expert-admins.
   * @param tableName name of table to unlock
   * @throws java.io.IOException if a remote or network exception occurs
   */
  public void unlockTable(final byte[] tableName) throws IOException {
    FTable.isLegalTableName(Bytes.toString(tableName));
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        LOG.info("Started unlock of " + Bytes.toString(tableName));
        MasterAdminProtos.UnlockTableRequest req = RequestConverter.buildUnlockTableRequest(tableName);
        masterAdmin.unlockTable(null, req);
        return null;
      }
    });
  }

  public void setTableState(final String tableName, final String state) throws IOException {
    setTableState(Bytes.toBytes(tableName), Bytes.toBytes(state));
  }

  /**
   * Set table state, use it carefully. For expert-admins.
   * @param tableName name of table to set state
   * @param state state that will set to
   * @throws java.io.IOException if a remote or network exception occurs
   */
  public void setTableState(final byte[] tableName, final byte[] state) throws IOException {
    FTable.isLegalTableName(Bytes.toString(tableName));
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        LOG.info("Started set " + Bytes.toString(tableName) + " 'state to " + Bytes.toString(state));
        MasterAdminProtos.SetTableStateRequest req = RequestConverter.buildSetTableStateRequest(tableName, state);
        masterAdmin.setTableState(null, req);
        return null;
      }
    });
  }

  public void waitTableAvailable(final String table, long timeoutMillis)
      throws IOException, InterruptedException {
    waitTableAvailable(Bytes.toBytes(table), timeoutMillis);
  }

  /**
   * @param table
   * @param timeoutMillis
   * @throws InterruptedException
   * @throws java.io.IOException
   */
  public void waitTableAvailable(byte[] table, long timeoutMillis)
      throws IOException, InterruptedException {
    long startWait = System.currentTimeMillis();
    while (!isTableAvailable(table)) {
      if (System.currentTimeMillis() - startWait >= timeoutMillis) {
        throw new IOException(
            "Timed out waiting for table to become available "
                + Bytes.toStringBinary(table));
      }
      Thread.sleep(200);
    }
  }

  public void waitTableAvailable(final String table) throws IOException,
      InterruptedException {
    waitTableAvailable(Bytes.toBytes(table));
  }

  public void waitTableAvailable(byte[] table) throws IOException,
      InterruptedException {
    long startWait = System.currentTimeMillis();
    while (!isTableAvailable(table)) {
      LOG.info("Wait " + (System.currentTimeMillis() - startWait) / 1000
          + " s, for " + Bytes.toString(table) + " to be Available.");
      Thread.sleep(1000);
    }
  }

  public void waitTableNotAvailable(final String table) throws IOException,
      InterruptedException {
    waitTableNotAvailable(Bytes.toBytes(table));
  }

  public void waitTableNotAvailable(byte[] table) throws IOException,
      InterruptedException {
    long startWait = System.currentTimeMillis();
    while (tableExists(table)) {
      LOG.info("Wait " + (System.currentTimeMillis() - startWait) / 1000
          + " s, for " + Bytes.toString(table) + " to be Available.");
      Thread.sleep(1000);
    }
  }

  public void waitTableEnabled(final String table, long timeoutMillis)
      throws InterruptedException, IOException {
    waitTableEnabled(Bytes.toBytes(table), timeoutMillis);
  }

  public void waitTableEnabled(byte[] table, long timeoutMillis)
      throws InterruptedException, IOException {
    long startWait = System.currentTimeMillis();
    while (!isTableEnabled(table)) {
      if (System.currentTimeMillis() - startWait >= timeoutMillis) {
        throw new IOException("Timed out waiting for table "
            + Bytes.toStringBinary(table));
      }
      Thread.sleep(200);
    }
  }

  public void waitTableDisabled(final String table, long timeoutMillis)
      throws InterruptedException, IOException {
    waitTableDisabled(Bytes.toBytes(table), timeoutMillis);
  }

  public void waitTableDisabled(byte[] table, long timeoutMillis)
      throws InterruptedException, IOException {
    long startWait = System.currentTimeMillis();
    while (!isTableDisabled(table)) {
      if (System.currentTimeMillis() - startWait >= timeoutMillis) {
        throw new IOException("Timed out waiting for table "
            + Bytes.toStringBinary(table));
      }
      Thread.sleep(200);
    }
  }

  public void waitTableNotLocked(final String table) throws IOException,
      InterruptedException {
    waitTableNotLocked(Bytes.toBytes(table));
  }

  public void waitTableNotLocked(byte[] table) throws IOException,
      InterruptedException {
    long startWait = System.currentTimeMillis();
    while (isTableLocked(table)) {
      LOG.info("Wait " + (System.currentTimeMillis() - startWait) / 1000
          + " s, for " + Bytes.toString(table) + " to be not locked.");
      Thread.sleep(1000);
    }
  }

  /**
   * Get the status of alter command - indicates how many entityGroups have
   * received the updated schema Asynchronous operation.
   *
   * @param tableName
   *          name of the table to get the status of
   * @return Pair indicating the number of entityGroups updated Pair.getFirst()
   *         is the entityGroups that are yet to be updated Pair.getSecond() is
   *         the total number of entityGroups of the table
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public Pair<Integer, Integer> getAlterStatus(final byte[] tableName)
      throws IOException {
    FTable.isLegalTableName(Bytes.toString(tableName));
    return execute(new MasterMonitorCallable<Pair<Integer, Integer>>() {
      @Override
      public Pair<Integer, Integer> call() throws ServiceException {
        GetSchemaAlterStatusRequest req = RequestConverter
            .buildGetSchemaAlterStatusRequest(tableName);
        GetSchemaAlterStatusResponse ret = masterMonitor.getSchemaAlterStatus(
            null, req);
        Pair<Integer, Integer> pair = new Pair<Integer, Integer>(
            Integer.valueOf(ret.getYetToUpdateEntityGroups()),
            Integer.valueOf(ret.getTotalEntityGroups()));
        return pair;
      }
    });
  }

  /**
   * Close a entityGroup. For expert-admins. Runs close on the
   * entityGroupserver. The master will not be informed of the close.
   *
   * @param entityGroupname
   *          entityGroup name to close
   * @param serverName
   *          If supplied, we'll use this location rather than the one currently
   *          in <code>.META.</code>
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public void closeEntityGroup(final String entityGroupname,
      final String serverName) throws IOException {
    closeEntityGroup(Bytes.toBytes(entityGroupname), serverName);
  }

  /**
   * Close a entityGroup. For expert-admins Runs close on the entityGroupserver.
   * The master will not be informed of the close.
   *
   * @param entityGroupname
   *          entityGroup name to close
   * @param serverName
   *          The servername of the entityGroupserver. 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 java.io.IOException
   *           if a remote or network exception occurs
   */
  public void closeEntityGroup(final byte[] entityGroupname,
      final String serverName) throws IOException {
    if (serverName != null) {
      if (("").equals(serverName.trim())) {
        throw new IllegalArgumentException(
            "The servername cannot be null or empty.");
      }
      MasterAdminProtos.GetEntityGroupResponse res = execute(new MasterAdminCallable<MasterAdminProtos.GetEntityGroupResponse>() {
        @Override
        public MasterAdminProtos.GetEntityGroupResponse call() throws ServiceException {
          return this.masterAdmin.getEntityGroup(null,
              RequestConverter.buildGetEntityGroupRequest(entityGroupname));
        }
      });
      if (!res.hasEgInfo()) {
        throw new UnknownEntityGroupException(
            Bytes.toStringBinary(entityGroupname));
      } else {
        closeEntityGroup(new ServerName(serverName),
            EntityGroupInfo.convert(res.getEgInfo()));
      }
    } else {
      MasterAdminProtos.GetEntityGroupResponse res = execute(new MasterAdminCallable<MasterAdminProtos.GetEntityGroupResponse>() {
        @Override
        public MasterAdminProtos.GetEntityGroupResponse call() throws ServiceException {
          return this.masterAdmin.getEntityGroup(null,
              RequestConverter.buildGetEntityGroupRequest(entityGroupname));
        }
      });
      if (!res.hasEgInfo()) {
        throw new UnknownEntityGroupException(
            Bytes.toStringBinary(entityGroupname));
      } else if (!res.hasServerName()) {
        throw new NoServerForEntityGroupException(
            Bytes.toStringBinary(entityGroupname));
      } else {
        closeEntityGroup(ServerName.convert(res.getServerName()),
            EntityGroupInfo.convert(res.getEgInfo()));
      }
    }
  }

  /**
   * Close a entityGroup. For expert-admins Runs close on the entityGroupserver.
   * The master will not be informed of the close.
   *
   * @param sn
   * @param egi
   * @throws java.io.IOException
   */
  public void closeEntityGroup(final ServerName sn, final EntityGroupInfo egi)
      throws IOException {
    AdminProtocol admin = this.connection.getAdmin(sn.getHostname(),
        sn.getPort());
    // Close the entityGroup without updating zk state.
    ProtobufUtil.closeEntityGroup(admin, egi, false);
  }

  /**
   * For expert-admins. Runs close on the fserver. Closes a entityGroup based on
   * the encoded entityGroup name. The fserver name is mandatory. If the
   * servername is provided then based on the online entityGroups in the specified
   * fserver the specified entityGroup will be closed. The master will not be
   * informed of the close. Note that the entityGroupName is the encoded entityGroupName.
   *
   * @param encodedEntityGroupName
   *          The encoded entityGroup name; i.e. the hash that makes up the
   *          entityGroup name suffix: e.g. if entityGroupName is
   *          <code>TestTable,0094429456,1289497600452.527db22f95c8a9e0116f0cc13c680396.</code>
   *          , then the encoded entityGroup name is:
   *          <code>527db22f95c8a9e0116f0cc13c680396</code>.
   * @param serverName
   *          The servername of the fserver. 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 entityGroup was closed, false if not.
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public boolean closeEntityGroupWithEncodedEntityGroupName(
      final String encodedEntityGroupName, final String serverName)
      throws IOException {
    if (null == serverName || ("").equals(serverName.trim())) {
      throw new IllegalArgumentException(
          "The servername cannot be null or empty.");
    }
    ServerName sn = new ServerName(serverName);
    AdminProtocol admin = this.connection.getAdmin(sn.getHostname(),
        sn.getPort());
    CloseEncodedEntityGroupRequest request = RequestConverter
        .buildCloseEncodedEntityGroupRequest(encodedEntityGroupName, false);
    try {
      CloseEncodedEntityGroupResponse response = admin.closeEncodedEntityGroup(
          null, request);
      boolean success = response.getSuccess();
      if (!success) {
        LOG.error("Not able to close the entityGroup " + encodedEntityGroupName
            + ".");
      }
      return success;
    } catch (ServiceException se) {
      throw ProtobufUtil.getRemoteException(se);
    }
  }

  /**
   * Get all the online entityGroups on a entityGroup server.
   */
  public List<EntityGroupInfo> getOnlineEntityGroups(final ServerName sn)
      throws IOException {
    AdminProtocol admin = this.connection.getAdmin(sn.getHostname(),
        sn.getPort());
    return ProtobufUtil.getOnlineEntityGroups(admin);
  }

  /**
   * Move the entityGroup <code>r</code> to <code>dest</code>.
   *
   * @param encodedEntityGroupName
   *          The encoded entityGroup name; i.e. the hash that makes up the
   *          entityGroup name suffix: e.g. if entityGroupname is
   *          <code>TestTable,0094429456,1289497600452.527db22f95c8a9e0116f0cc13c680396.</code>
   *          , then the encoded entityGroup name is:
   *          <code>527db22f95c8a9e0116f0cc13c680396</code>.
   * @param destServerName
   *          The servername of the destination entityGroupserver. 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 com.alibaba.wasp.UnknownEntityGroupException
   *           Thrown if we can't find a entityGroup named
   *           <code>encodedEntityGroupName</code>
   * @throws com.alibaba.wasp.ZooKeeperConnectionException
   * @throws com.alibaba.wasp.MasterNotRunningException
   */
  public void move(final byte[] encodedEntityGroupName,
      final byte[] destServerName) throws UnknownEntityGroupException,
      MasterNotRunningException, ZooKeeperConnectionException {
    MasterAdminKeepAliveConnection master = connection
        .getKeepAliveMasterAdmin();
    try {
      MasterAdminProtos.MoveEntityGroupRequest request = RequestConverter
          .buildMoveEntityGroupRequest(encodedEntityGroupName, destServerName);
      master.moveEntityGroup(null, request);
    } catch (ServiceException se) {
      IOException ioe = ProtobufUtil.getRemoteException(se);
      if (ioe instanceof UnknownEntityGroupException) {
        throw (UnknownEntityGroupException) ioe;
      }
      LOG.error("Unexpected exception: " + se
          + " from calling HMaster.moveEntityGroup");
    } finally {
      master.close();
    }
  }

  /**
   * @param entityGroupName
   *          EntityGroup name to assign.
   * @throws com.alibaba.wasp.MasterNotRunningException
   * @throws com.alibaba.wasp.ZooKeeperConnectionException
   * @throws java.io.IOException
   */
  public void assign(final byte[] entityGroupName)
      throws MasterNotRunningException, ZooKeeperConnectionException,
      IOException {
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        MasterAdminProtos.AssignEntityGroupRequest request = RequestConverter
            .buildAssignEntityGroupRequest(entityGroupName);
        masterAdmin.assignEntityGroup(null, request);
        return null;
      }
    });
  }

  /**
   * Unassign a entityGroup from current hosting entityGroupserver. EntityGroup
   * will then be assigned to a entityGroupserver chosen at random. EntityGroup
   * could be reassigned back to the same server. Use
   * {@link #move(byte[], byte[])} if you want to control the entityGroup
   * movement.
   *
   * @param entityGroupName
   *          EntityGroup to unassign. Will clear any existing EntityGroupPlan
   *          if one found.
   * @param force
   *          If true, force unassign (Will remove entityGroup from
   *          entityGroups-in-transition too if present. If results in double
   *          assignment use hbck -fix to resolve. To be used by experts).
   * @throws com.alibaba.wasp.MasterNotRunningException
   * @throws com.alibaba.wasp.ZooKeeperConnectionException
   * @throws java.io.IOException
   */
  public void unassign(final byte[] entityGroupName, final boolean force)
      throws MasterNotRunningException, ZooKeeperConnectionException,
      IOException {
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        MasterAdminProtos.UnassignEntityGroupRequest request = RequestConverter
            .buildUnassignEntityGroupRequest(entityGroupName, force);
        masterAdmin.unassignEntityGroup(null, request);
        return null;
      }
    });
  }

  /**
   * Special method, only used by hbck.
   */
  public void offline(final byte[] entityGroupName) throws IOException {
    MasterAdminKeepAliveConnection master = connection
        .getKeepAliveMasterAdmin();
    try {
      master.offlineEntityGroup(null,
          RequestConverter.buildOfflineEntityGroupRequest(entityGroupName));
    } catch (ServiceException se) {
      throw ProtobufUtil.getRemoteException(se);
    } finally {
      master.close();
    }
  }

  /**
   * 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 {
    MasterAdminKeepAliveConnection master = connection
        .getKeepAliveMasterAdmin();
    try {
      MasterAdminProtos.SetBalancerRunningRequest req = RequestConverter
          .buildSetBalancerRunningRequest(on, synchronous);
      return master.setBalancerRunning(null, req).getPrevBalanceValue();
    } catch (ServiceException se) {
      IOException ioe = ProtobufUtil.getRemoteException(se);
      if (ioe instanceof MasterNotRunningException) {
        throw (MasterNotRunningException) ioe;
      }
      if (ioe instanceof ZooKeeperConnectionException) {
        throw (ZooKeeperConnectionException) ioe;
      }

      // Throwing MasterNotRunningException even though not really valid in
      // order to not
      // break interface by adding additional exception type.
      throw new MasterNotRunningException(
          "Unexpected exception when calling balanceSwitch", se);
    } finally {
      master.close();
    }
  }

  /**
   * Turn the load balancer on or off.
   *
   * @param on
   *          If true, enable balancer. If false, disable balancer.
   * @return Previous balancer value
   */
  public boolean setBalancerRunning(final boolean on)
      throws MasterNotRunningException, ZooKeeperConnectionException {
    return setBalancerRunning(on, false);
  }

  /**
   * Invoke the balancer. Will run the balancer and if entityGroups 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, ServiceException {
    MasterAdminKeepAliveConnection master = connection
        .getKeepAliveMasterAdmin();
    try {
      return master.balance(null, RequestConverter.buildBalanceRequest())
          .getBalancerRan();
    } finally {
      master.close();
    }
  }

  /**
   * Split a table or an individual entityGroup. Asynchronous operation.
   *
   * @param tableNameOrEntityGroupName
   *          table to entityGroup to split
   * @param splitPoint
   *          the explicit position to split on
   * @throws java.io.IOException
   * @throws InterruptedException
   */
  public void split(final String tableNameOrEntityGroupName,
      final String splitPoint) throws IOException, InterruptedException {
    split(Bytes.toBytes(tableNameOrEntityGroupName), splitPoint == null ? null
        : Bytes.toBytes(splitPoint));
  }

  /**
   * Split a table or an individual entityGroup. Asynchronous operation.
   *
   * @param tableNameOrEntityGroupName
   *          table to entityGroup to split
   * @param splitPoint
   *          the explicit position to split on
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   * @throws InterruptedException
   *           interrupt exception occurred
   */
  public void split(final byte[] tableNameOrEntityGroupName,
      final byte[] splitPoint) throws IOException, InterruptedException {
    if (splitPoint == null || splitPoint.length == 0) {
      throw new IncorrectParameterException("SplitPoint can't be null.");
    }
    if (isEntityGroupName(tableNameOrEntityGroupName)) {
      Pair<EntityGroupInfo, ServerName> entityGroupServerPair = getEntityGroup(tableNameOrEntityGroupName);
      if (entityGroupServerPair != null) {
        if (entityGroupServerPair.getSecond() == null) {
          throw new NoServerForEntityGroupException(
              Bytes.toStringBinary(tableNameOrEntityGroupName));
        } else {
          split(entityGroupServerPair.getSecond(),
              entityGroupServerPair.getFirst(), splitPoint);
        }
      }
    } else {
      String tableNameString = tableNameString(tableNameOrEntityGroupName);
      MasterAdminProtos.GetTableEntityGroupsResponse res = execute(new MasterAdminCallable<MasterAdminProtos.GetTableEntityGroupsResponse>() {
        @Override
        public MasterAdminProtos.GetTableEntityGroupsResponse call() throws ServiceException {
          MasterAdminProtos.GetTableEntityGroupsRequest req = RequestConverter
              .buildGetTableEntityGroupsRequest(tableNameOrEntityGroupName);
          return this.masterAdmin.getTableEntityGroups(null, req);
        }
      });
      List<Pair<EntityGroupInfo, ServerName>> pairs = new ArrayList<Pair<EntityGroupInfo, ServerName>>(
          res.getEntityGroupList().size());
      for (MasterAdminProtos.GetEntityGroupResponse response : res.getEntityGroupList()) {
        pairs.add(new Pair<EntityGroupInfo, ServerName>(EntityGroupInfo
            .convert(response.getEgInfo()), ServerName.convert(response
            .getServerName())));
      }
      if (pairs == null || pairs.size() == 0) {
        throw new TableNotFoundException(tableNameString);
      }

      for (Pair<EntityGroupInfo, ServerName> pair : pairs) {
        // May not be a server for a particular row
        if (pair.getSecond() == null)
          continue;
        EntityGroupInfo eg = pair.getFirst();
        // check for parents
        if (eg.isSplitParent())
          continue;
        // if a split point given, only split that particular entityGroup
        if (splitPoint != null && !eg.containsRow(splitPoint))
          continue;
        // call out to entityGroup server to do split now
        split(pair.getSecond(), pair.getFirst(), splitPoint);
      }
    }
  }

  private void split(final ServerName sn, final EntityGroupInfo egi,
      byte[] splitPoint) throws IOException {
    AdminProtocol admin = this.connection.getAdmin(sn.getHostname(),
        sn.getPort());
    ProtobufUtil.split(admin, egi, splitPoint);
  }

  /**
   * Add a column to an existing table. Asynchronous operation.
   *
   * @param tableName
   *          name of the table to add column to
   * @param column
   *          the field to be added
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public void addColumn(final String tableName, Field 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 java.io.IOException
   *           if a remote or network exception occurs
   */
  public void addColumn(final byte[] tableName, final Field column)
      throws IOException {
    FTable newTable = this.getTableDescriptor(tableName);
    newTable.getColumns().put(column.getName(), column);
    modifyTable(tableName, newTable);
  }

  /**
   * Delete a column from a table. Asynchronous operation.
   *
   * @param tableName
   *          name of table
   * @param columnName
   *          name of column to be deleted
   * @throws java.io.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 java.io.IOException
   *           if a remote or network exception occurs
   */
  public void deleteColumn(final byte[] tableName, final byte[] columnName)
      throws IOException {
    FTable newTable = this.getTableDescriptor(tableName);
    newTable.getColumns().remove(columnName);
    modifyTable(tableName, newTable);
  }

  /**
   * Modify an existing column family on a table. Asynchronous operation.
   *
   * @param tableName
   *          name of table
   * @param field
   *          new field to use
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public void modifyColumn(final String tableName, Field field)
      throws IOException {
    modifyColumn(Bytes.toBytes(tableName), field);
  }

  /**
   * Modify an existing column family on a table. Asynchronous operation.
   *
   * @param tableName
   *          name of table
   * @param field
   *          new field to use
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public void modifyColumn(final byte[] tableName, final Field field)
      throws IOException {
    FTable newTable = this.getTableDescriptor(tableName);
    Field oldField = newTable.getColumns().get(field.getName());
    if (oldField != null) {
      newTable.getColumns().put(field.getName(), field);
    }
    modifyTable(tableName, newTable);
  }

  /**
   * Get a connection to the currently set master.
   *
   * @return proxy connection to master server for this instance
   * @throws org.apache.hadoop.hbase.MasterNotRunningException
   *           if the master is not running
   * @throws org.apache.hadoop.hbase.ZooKeeperConnectionException
   *           if unable to connect to zookeeper
   */
  public FMasterAdminProtocol getMaster() throws IOException {
    return this.connection.getMasterAdmin();
  }

  /**
   * 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 tableDescriptor
   *          modified description of the table
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public void modifyTable(final byte[] tableName, final FTable tableDescriptor)
      throws IOException {
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        MasterAdminProtos.ModifyTableRequest request = RequestConverter.buildModifyTableRequest(
            tableName, tableDescriptor);
        masterAdmin.modifyTable(null, request);
        return null;
      }
    });
  }

  /**
   * @param tableNameOrEntityGroupName
   *          Name of a table or name of a entityGroup.
   * @return a pair of EntityGroupInfo and ServerName if
   *         <code>tableNameOrEntityGroupName</code> is a verified entityGroup
   *         name (we call
   *         {@link com.alibaba.wasp.meta.FMetaReader#getEntityGroupLocation(org.apache.hadoop.conf.Configuration, com.alibaba.wasp.EntityGroupInfo)}
   *         (com.alibaba.wasp.EntityGroupInfo)} else null. Throw an exception if
   *         <code>tableNameOrEntityGroupName</code> is null.
   * @throws java.io.IOException
   */
  Pair<EntityGroupInfo, ServerName> getEntityGroup(
      final byte[] tableNameOrEntityGroupName) throws IOException {
    if (tableNameOrEntityGroupName == null) {
      throw new IllegalArgumentException(
          "Pass a table name or entityGroup name");
    }
    MasterAdminProtos.GetEntityGroupWithScanResponse res = execute(new MasterAdminCallable<MasterAdminProtos.GetEntityGroupWithScanResponse>() {
      @Override
      public MasterAdminProtos.GetEntityGroupWithScanResponse call() throws ServiceException {
        return masterAdmin.getEntityGroupWithScan(null, RequestConverter
            .buildGetEntityGroupWithScanRequest(tableNameOrEntityGroupName));
      }
    });

    Pair<EntityGroupInfo, ServerName> pair = new Pair<EntityGroupInfo, ServerName>(
        EntityGroupInfo.convert(res.getEgInfo()), ServerName.convert(res
        .getServerName()));
    return pair;
  }

  /**
   * @param tableNameOrEntityGroupName
   *          Name of a table or name of a entityGroup.
   * @return True if <code>tableNameOrEntityGroupName</code> is a verified
   *         entityGroup name else false. Throw an exception if
   *         <code>tableNameOrEntityGroupName</code> is null.
   * @throws java.io.IOException
   */
  private boolean isEntityGroupName(final byte[] tableNameOrEntityGroupName)
      throws IOException {
    if (tableNameOrEntityGroupName == null) {
      throw new IllegalArgumentException(
          "Pass a table name or entityGroup name");
    }

    MasterAdminProtos.GetEntityGroupResponse res = execute(new MasterAdminCallable<MasterAdminProtos.GetEntityGroupResponse>() {
      @Override
      public MasterAdminProtos.GetEntityGroupResponse call() throws ServiceException {
        return masterAdmin.getEntityGroup(null, RequestConverter
            .buildGetEntityGroupRequest(tableNameOrEntityGroupName));
      }
    });

    return res.hasServerName();
  }

  /**
   * Convert the table name byte array into a table name string and check if
   * table exists or not.
   *
   * @param tableNameBytes
   *          Name of a table.
   * @return tableName in string form.
   * @throws java.io.IOException
   *           if a remote or network exception occurs.
   * @throws com.alibaba.wasp.TableNotFoundException
   *           if table does not exist.
   */
  private String tableNameString(final byte[] tableNameBytes)
      throws IOException {
    MasterAdminProtos.TableExistsResponse res = execute(new MasterAdminCallable<MasterAdminProtos.TableExistsResponse>() {
      @Override
      public MasterAdminProtos.TableExistsResponse call() throws ServiceException {
        return masterAdmin.tableExists(null,
            RequestConverter.buildTableExistsRequest(tableNameBytes));
      }
    });
    String tableNameString = Bytes.toString(tableNameBytes);
    if (!res.getExist()) {
      throw new TableNotFoundException(tableNameString);
    }
    return tableNameString;
  }

  /**
   * Shuts down the Wasp cluster
   *
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public synchronized void shutdown() throws IOException {
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        masterAdmin.shutdown(null, MasterAdminProtos.ShutdownRequest.newBuilder().build());
        return null;
      }
    });
  }

  /**
   * Shuts down the current Wasp master only. Does not shutdown the cluster.
   *
   * @see #shutdown()
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public synchronized void stopMaster() throws IOException {
    execute(new MasterAdminCallable<Void>() {
      @Override
      public Void call() throws ServiceException {
        masterAdmin.stopMaster(null, MasterAdminProtos.StopMasterRequest.newBuilder().build());
        return null;
      }
    });
  }

  /**
   * Stop the designated entityGroupserver
   *
   * @param hostnamePort
   *          Hostname and port delimited by a <code>:</code> as in
   *          <code>example.org:1234</code>
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public synchronized void stopEntityGroupServer(final String hostnamePort)
      throws IOException {
    String hostname = Addressing.parseHostname(hostnamePort);
    int port = Addressing.parsePort(hostnamePort);
    AdminProtocol admin = this.connection.getAdmin(hostname, port);
    StopServerRequest request = RequestConverter
        .buildStopServerRequest("Called by admin client "
            + this.connection.toString());
    try {
      admin.stopServer(null, request);
    } catch (ServiceException se) {
      throw ProtobufUtil.getRemoteException(se);
    }
  }

  /**
   * @return cluster status
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public ClusterStatus getClusterStatus() throws IOException {
    return execute(new MasterMonitorCallable<ClusterStatus>() {
      @Override
      public ClusterStatus call() throws ServiceException {
        GetClusterStatusRequest req = RequestConverter
            .buildGetClusterStatusRequest();
        return ClusterStatus.convert(masterMonitor.getClusterStatus(null, req)
            .getClusterStatus());
      }
    });
  }

  /**
   * @return Configuration used by the instance.
   */
  public Configuration getConfiguration() {
    return this.conf;
  }

  /**
   * Check to see if Wasp is running. Throw an exception if not. We consider
   * that Wasp is running if ZooKeeper and Master are running.
   *
   * @param conf
   *          system configuration
   * @throws com.alibaba.wasp.MasterNotRunningException
   *           if the master is not running
   * @throws com.alibaba.wasp.ZooKeeperConnectionException
   *           if unable to connect to zookeeper
   */
  public static void checkWaspAvailable(Configuration conf)
      throws MasterNotRunningException, ZooKeeperConnectionException,
      ServiceException {
    Configuration copyOfConf = WaspConfiguration.create(conf);

    // We set it to make it fail as soon as possible if Wasp is not available
    copyOfConf.setInt("wasp.client.retries.number", 1);
    copyOfConf.setInt("zookeeper.recovery.retry", 0);

    FConnectionManager.FConnectionImplementation connection = (FConnectionManager.FConnectionImplementation) FConnectionManager
        .getConnection(copyOfConf);

    try {
      // Check ZK first.
      // If the connection exists, we may have a connection to ZK that does
      // not work anymore
      ZooKeeperWatcher zkw = null;
      try {
        zkw = connection.getZooKeeperWatcher();
        zkw.getRecoverableZooKeeper().getZooKeeper()
            .exists(zkw.baseZNode, false);

      } catch (IOException e) {
        throw new ZooKeeperConnectionException("Can't connect to ZooKeeper", e);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new ZooKeeperConnectionException("Can't connect to ZooKeeper", e);
      } catch (KeeperException e) {
        throw new ZooKeeperConnectionException("Can't connect to ZooKeeper", e);
      } finally {
        if (zkw != null) {
          zkw.close();
        }
      }

      // Check Master
      connection.isMasterRunning();

    } finally {
      connection.close();
    }
  }

  /**
   * get the entityGroups of a given table.
   *
   * @param tableName
   *          the name of the table
   * @return Ordered list of {@link com.alibaba.wasp.EntityGroupInfo}.
   * @throws java.io.IOException
   */
  public List<EntityGroupInfo> getTableEntityGroups(final byte[] tableName)
      throws IOException {
    MasterAdminProtos.GetTableEntityGroupsResponse res = execute(new MasterAdminCallable<MasterAdminProtos.GetTableEntityGroupsResponse>() {
      @Override
      public MasterAdminProtos.GetTableEntityGroupsResponse call() throws ServiceException {
        MasterAdminProtos.GetTableEntityGroupsRequest req = RequestConverter
            .buildGetTableEntityGroupsRequest(tableName);
        return this.masterAdmin.getTableEntityGroups(null, req);
      }
    });
    List<EntityGroupInfo> egis = new ArrayList<EntityGroupInfo>();
    for (MasterAdminProtos.GetEntityGroupResponse response : res.getEntityGroupList()) {
      egis.add(EntityGroupInfo.convert(response.getEgInfo()));
    }
    return egis;
  }

  /**
   * Gets all the entityGroups and their address for this table.
   * <p>
   * This is mainly useful for the MapReduce integration.
   *
   * @return A map of EntityGroupInfo with it's server address
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public NavigableMap<EntityGroupInfo, ServerName> getEntityGroupLocations(
      final byte[] tableName) throws IOException {
    MasterAdminProtos.GetEntityGroupsResponse res = execute(new MasterAdminCallable<MasterAdminProtos.GetEntityGroupsResponse>() {
      @Override
      public MasterAdminProtos.GetEntityGroupsResponse call() throws ServiceException {
        MasterAdminProtos.GetEntityGroupsRequest req = RequestConverter
            .buildGetEntityGroupsRequest(tableName);
        return this.masterAdmin.getEntityGroups(null, req);
      }
    });
    NavigableMap<EntityGroupInfo, ServerName> entityGroups = null;
    if (!res.getEntityGroupList().isEmpty()) {
      entityGroups = new TreeMap<EntityGroupInfo, ServerName>();
      for (MasterAdminProtos.GetEntityGroupResponse response : res.getEntityGroupList()) {
        entityGroups.put(EntityGroupInfo.convert(response.getEgInfo()),
            ServerName.convert(response.getServerName()));
      }
    }
    return entityGroups;
  }

  public void truncate(String tableName) throws TableNotDisabledException,
      IOException {
    truncate(Bytes.toBytes(tableName));
  }

  public void truncate(final byte[] tableName)
      throws TableNotDisabledException, IOException {
    // Disable table
    disableTable(tableName);
    // Truncate table
    List<MasterAdminProtos.TruncateTableResponse> responses = new ArrayList<MasterAdminProtos.TruncateTableResponse>();
    if (this.connection.isTableDisabled(tableName)) {
      MasterAdminProtos.TruncateTableResponse res = execute(new MasterAdminCallable<MasterAdminProtos.TruncateTableResponse>() {
        @Override
        public MasterAdminProtos.TruncateTableResponse call() throws ServiceException {
          MasterAdminProtos.TruncateTableRequest request = RequestConverter
              .buildTruncateTableRequest(tableName);
          return this.masterAdmin.truncateTable(null, request);
        }
      });
      responses.add(res);
    } else {
      throw new TableNotDisabledException(tableName);
    }
    // Wait truncate finished
    try {
      waitTableNotLocked(tableName);
    } catch (InterruptedException e) {
    }
    // Enable table
    enableTable(tableName);
  }

  @Override
  public void close() throws IOException {
    if (this.connection != null) {
      this.connection.close();
    }
  }

  /**
   * Get tableDescriptors
   *
   * @param tableNames
   *          List of table names
   * @return FTD[] the tableDescriptor
   * @throws java.io.IOException
   *           if a remote or network exception occurs
   */
  public FTable[] getTableDescriptors(List<String> tableNames)
      throws IOException {
    return this.connection.getFTableDescriptors(tableNames);
  }

  /**
   * @see {@link #execute(MasterAdminCallable<V>)}
   */
  private abstract static class MasterAdminCallable<V> implements Callable<V> {
    protected MasterAdminKeepAliveConnection masterAdmin;
  }

  /**
   * @see {@link #execute(MasterMonitorCallable<V>)}
   */
  private abstract static class MasterMonitorCallable<V> implements Callable<V> {
    protected MasterMonitorKeepAliveConnection masterMonitor;
  }

  /**
   * This method allows to execute a function requiring a connection to master
   * without having to manage the connection creation/close. Create a
   * {@link MasterAdminCallable} to use it.
   */
  private <V> V execute(MasterAdminCallable<V> function) throws IOException {
    function.masterAdmin = connection.getKeepAliveMasterAdmin();
    try {
      return executeCallable(function);
    } finally {
      function.masterAdmin.close();
    }
  }

  /**
   * This method allows to execute a function requiring a connection to master
   * without having to manage the connection creation/close. Create a
   * {@link MasterAdminCallable} to use it.
   */
  private <V> V execute(MasterMonitorCallable<V> function) throws IOException {
    function.masterMonitor = connection.getKeepAliveMasterMonitor();
    try {
      return executeCallable(function);
    } finally {
      function.masterMonitor.close();
    }
  }

  /**
   * Helper function called by other execute functions.
   */
  private <V> V executeCallable(Callable<V> function) throws IOException {
    try {
      return function.call();
    } catch (RemoteException re) {
      throw re.unwrapRemoteException();
    } catch (IOException e) {
      throw e;
    } catch (ServiceException se) {
      throw ProtobufUtil.getRemoteException(se);
    } catch (Exception e) {
      // This should not happen...
      throw new IOException("Unexpected exception when calling master", e);
    }
  }
}
TOP

Related Classes of com.alibaba.wasp.client.WaspAdmin$MasterAdminCallable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.