com.alibaba.wasp.client.WaspAdmin.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.wasp.client.WaspAdmin.java

Source

/**
 * 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);
        }
    }
}