Package org.apache.hcatalog.hbase.snapshot

Source Code of org.apache.hcatalog.hbase.snapshot.ZKUtil

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hcatalog.hbase.snapshot;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hcatalog.hbase.snapshot.transaction.thrift.*;
import org.apache.thrift.TBase;
import org.apache.thrift.TDeserializer;
import org.apache.thrift.TSerializer;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;

class ZKUtil {

    private int              DEFAULT_SESSION_TIMEOUT = 1000000;
    private ZooKeeper        zkSession;
    private String           baseDir;
    private String           connectString;
    private static final Log LOG = LogFactory.getLog(ZKUtil.class);

    static enum UpdateMode {
        APPEND, REMOVE, KEEP_ALIVE
    };

    ZKUtil(String connection, String baseDir) {
        this.connectString = connection;
        this.baseDir = baseDir;
    }

    /**
     * This method creates znodes related to table.
     *
     * @param table The name of the table.
     * @param families The list of column families of the table.
     * @throws IOException
     */
    void setUpZnodesForTable(String table, List<String> families)
            throws IOException {

        String transactionDataTablePath = PathUtil.getTxnDataPath(baseDir, table);
        ensurePathExists(transactionDataTablePath, null, Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        for (String cf : families) {
            String runningDataPath = PathUtil.getRunningTxnInfoPath(
                    this.baseDir, table, cf);
            ensurePathExists(runningDataPath, null, Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
            String abortDataPath = PathUtil.getAbortInformationPath(
                    this.baseDir, table, cf);
            ensurePathExists(abortDataPath, null, Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        }

    }

    /**
     * This method ensures that a given path exists in zookeeper. If the path
     * does not exists, it creates one.
     *
     * @param path The path of znode that is required to exist.
     * @param data The data to be associated with the znode.
     * @param acl The ACLs required.
     * @param flags The CreateMode for the znode.
     * @throws IOException
     */
    void ensurePathExists(String path, byte[] data, List<ACL> acl,
            CreateMode flags) throws IOException {
        String[] dirs = path.split("/");
        String parentPath = "";
        for (String subDir : dirs) {
            if (subDir.equals("") == false) {
                parentPath = parentPath + "/" + subDir;
                try {
                    Stat stat = getSession().exists(parentPath, false);
                    if (stat == null) {
                        getSession().create(parentPath, data, acl, flags);
                    }
                } catch (Exception e) {
                    throw new IOException("Exception while creating path "
                            + parentPath, e);
                }
            }
        }

    }

    /**
     * This method returns a list of columns of a table which were used in any
     * of the transactions.
     *
     * @param tableName The name of table.
     * @return List<String> The list of column families in table.
     * @throws IOException
     */
    List<String> getColumnFamiliesOfTable(String tableName) throws IOException {
        String path = PathUtil.getTxnDataPath(baseDir, tableName);
        List<String> children = null;
        List<String> columnFamlies = new ArrayList<String>();
        try {
            children = getSession().getChildren(path, false);
        } catch (KeeperException e) {
            LOG.warn("Caught: ", e);
            throw new IOException("Exception while obtaining columns of table.",e);
        } catch (InterruptedException e) {
            LOG.warn("Caught: ", e);
            throw new IOException("Exception while obtaining columns of table.",e);
        }

        for (String child : children) {
            if ((child.contains("idgen") == false)
                    && (child.contains("_locknode_") == false)) {
                columnFamlies.add(child);
            }
        }
        return columnFamlies;
    }

    /**
     * This method returns a time stamp for use by the transactions.
     *
     * @return long The current timestamp in zookeeper.
     * @throws IOException
     */
    long getTimeStamp() throws IOException {
        long timeStamp;
        Stat stat;
        String clockPath = PathUtil.getClockPath(this.baseDir);
        ensurePathExists(clockPath, null, Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        try {
            getSession().exists(clockPath, false);
            stat = getSession().setData(clockPath, null, -1);

        } catch (KeeperException e) {
            LOG.warn("Caught: ", e);
            throw new IOException("Exception while obtaining timestamp ", e);
        } catch (InterruptedException e) {
            LOG.warn("Caught: ", e);
            throw new IOException("Exception while obtaining timestamp ", e);
        }
        timeStamp = stat.getMtime();
        return timeStamp;
    }

    /**
     * This method returns the next revision number to be used for any
     * transaction purposes.
     *
     * @param tableName The name of the table.
     * @return revision number The revision number last used by any transaction.
     * @throws IOException
     */
    long nextId(String tableName) throws IOException {
        String idNode = PathUtil.getRevisionIDNode(this.baseDir, tableName);
        ensurePathExists(idNode, Bytes.toBytes("0"), Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        String lockNode = PathUtil.getLockManagementNode(idNode);
        ensurePathExists(lockNode, null, Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        IDGenerator idf = new IDGenerator(getSession(), tableName, idNode);
        long id = idf.obtainID();
        return id;
    }

    /**
     * The latest used revision id of the table.
     *
     * @param tableName The name of the table.
     * @return the long The revision number to use by any transaction.
     * @throws IOException Signals that an I/O exception has occurred.
     */
    long currentID(String tableName) throws IOException{
        String idNode = PathUtil.getRevisionIDNode(this.baseDir, tableName);
        ensurePathExists(idNode, Bytes.toBytes("0"), Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        String lockNode = PathUtil.getLockManagementNode(idNode);
        ensurePathExists(lockNode, null, Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        IDGenerator idf = new IDGenerator(getSession(), tableName, idNode);
        long id = idf.readID();
        return id;
    }

    /**
     * This methods retrieves the list of transaction information associated
     * with each column/column family of a table.
     *
     * @param path The znode path
     * @return List of FamilyRevision The list of transactions in the given path.
     * @throws IOException
     */
    List<FamilyRevision> getTransactionList(String path)
            throws IOException {

        byte[] data = getRawData(path, new Stat());
        ArrayList<FamilyRevision> wtxnList = new ArrayList<FamilyRevision>();
        if (data == null) {
            return wtxnList;
        }
        StoreFamilyRevisionList txnList = new StoreFamilyRevisionList();
        deserialize(txnList, data);
        Iterator<StoreFamilyRevision> itr = txnList.getRevisionListIterator();

        while (itr.hasNext()) {
            StoreFamilyRevision wtxn = itr.next();
            wtxnList.add(new FamilyRevision(wtxn.getRevision(), wtxn
                    .getTimestamp()));
        }

        return wtxnList;
    }

    /**
     * This method returns the data associated with the path in zookeeper.
     *
     * @param path The znode path
     * @param stat Zookeeper stat
     * @return byte array The data stored in the znode.
     * @throws IOException
     */
    byte[] getRawData(String path, Stat stat) throws IOException {
        byte[] data = null;
        try {
            data = getSession().getData(path, false, stat);
        } catch (Exception e) {
            throw new IOException(
                    "Exception while obtaining raw data from zookeeper path "
                            + path, e);
        }
        return data;
    }

    /**
     * This method created the basic znodes in zookeeper for revision
     * management.
     *
     * @throws IOException
     */
    void createRootZNodes() throws IOException {
        String txnBaseNode = PathUtil.getTransactionBasePath(this.baseDir);
        String clockNode = PathUtil.getClockPath(this.baseDir);
        ensurePathExists(txnBaseNode, null, Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        ensurePathExists(clockNode, null, Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
    }

    /**
     * This method closes the zookeeper session.
     */
    void closeZKConnection() {
        if (zkSession != null) {
            try {
                zkSession.close();
            } catch (InterruptedException e) {
                LOG.warn("Close failed: ", e);
            }
            zkSession = null;
            LOG.info("Disconnected to ZooKeeper");
        }
    }

    /**
     * This method returns a zookeeper session. If the current session is closed,
     * then a new session is created.
     *
     * @return ZooKeeper An instance of zookeeper client.
     * @throws IOException
     */
     ZooKeeper getSession() throws IOException {
        if (zkSession == null || zkSession.getState() == States.CLOSED) {
            synchronized (this) {
                if (zkSession == null || zkSession.getState() == States.CLOSED) {
                    zkSession = new ZooKeeper(this.connectString,
                            this.DEFAULT_SESSION_TIMEOUT, new ZKWatcher());
                }
            }
        }
        return zkSession;
    }

    /**
     * This method updates the transaction data related to a znode.
     *
     * @param String The path to the transaction data.
     * @param FamilyRevision The FamilyRevision to be updated.
     * @param UpdateMode The mode to update like append, update, remove.
     * @throws IOException
     */
    void updateData(String path, FamilyRevision updateTx, UpdateMode mode)
            throws IOException {

        if (updateTx == null) {
            throw new IOException(
                    "The transaction to be updated found to be null.");
        }
        List<FamilyRevision> currentData = getTransactionList(path);
        List<FamilyRevision> newData = new ArrayList<FamilyRevision>();
        boolean dataFound = false;
        long updateVersion = updateTx.getRevision();
        for (FamilyRevision tranx : currentData) {
            if (tranx.getRevision() != updateVersion) {
                newData.add(tranx);
            } else {
                dataFound = true;
            }
        }
        switch (mode) {
            case REMOVE:
                if (dataFound == false) {
                    throw new IOException(
                            "The transaction to be removed not found in the data.");
                }
                LOG.info("Removed trasaction : " + updateTx.toString());
                break;
            case KEEP_ALIVE:
                if (dataFound == false) {
                    throw new IOException(
                            "The transaction to be kept alove not found in the data. It might have been expired.");
                }
                newData.add(updateTx);
                LOG.info("keep alive of transaction : " + updateTx.toString());
                break;
            case APPEND:
                if (dataFound == true) {
                    throw new IOException(
                            "The data to be appended already exists.");
                }
                newData.add(updateTx);
                LOG.info("Added transaction : " + updateTx.toString());
                break;
        }

        // For serialization purposes.
        List<StoreFamilyRevision> newTxnList = new ArrayList<StoreFamilyRevision>();
        for (FamilyRevision wtxn : newData) {
            StoreFamilyRevision newTxn = new StoreFamilyRevision(wtxn.getRevision(),
                    wtxn.getExpireTimestamp());
            newTxnList.add(newTxn);
        }
        StoreFamilyRevisionList wtxnList = new StoreFamilyRevisionList(newTxnList);
        byte[] newByteData = serialize(wtxnList);

        Stat stat = null;
        try {
            stat = zkSession.setData(path, newByteData, -1);
        } catch (KeeperException e) {
            throw new IOException(
                    "Exception while updating trasactional data. ", e);
        } catch (InterruptedException e) {
            throw new IOException(
                    "Exception while updating trasactional data. ", e);
        }

        if (stat != null) {
            LOG.info("Transaction list stored at " + path + ".");
        }

    }

    /**
     * Refresh transactions on a given transaction data path.
     *
     * @param path The path to the transaction data.
     * @throws IOException Signals that an I/O exception has occurred.
     */
    void refreshTransactions(String path) throws IOException{
        List<FamilyRevision> currentData = getTransactionList(path);
        List<FamilyRevision> newData = new ArrayList<FamilyRevision>();

        for (FamilyRevision tranx : currentData) {
            if (tranx.getExpireTimestamp() > getTimeStamp()) {
                newData.add(tranx);
            }
        }

        if(newData.equals(currentData) == false){
            List<StoreFamilyRevision> newTxnList = new ArrayList<StoreFamilyRevision>();
            for (FamilyRevision wtxn : newData) {
                StoreFamilyRevision newTxn = new StoreFamilyRevision(wtxn.getRevision(),
                        wtxn.getExpireTimestamp());
                newTxnList.add(newTxn);
            }
            StoreFamilyRevisionList wtxnList = new StoreFamilyRevisionList(newTxnList);
            byte[] newByteData = serialize(wtxnList);

            try {
                zkSession.setData(path, newByteData, -1);
            } catch (KeeperException e) {
                throw new IOException(
                        "Exception while updating trasactional data. ", e);
            } catch (InterruptedException e) {
                throw new IOException(
                        "Exception while updating trasactional data. ", e);
            }

        }

    }

    /**
     * Delete table znodes.
     *
     * @param tableName the hbase table name
     * @throws IOException Signals that an I/O exception has occurred.
     */
    void deleteZNodes(String tableName) throws IOException {
        String transactionDataTablePath = PathUtil.getTxnDataPath(baseDir,
                tableName);
        deleteRecursively(transactionDataTablePath);
    }

    void deleteRecursively(String path) throws IOException {
        try {
            List<String> children = getSession().getChildren(path, false);
            if (children.size() != 0) {
                for (String child : children) {
                    deleteRecursively(path + "/" + child);
                }
            }
            getSession().delete(path, -1);
        } catch (KeeperException e) {
            throw new IOException(
                    "Exception while deleting path " + path + ".", e);
        } catch (InterruptedException e) {
            throw new IOException(
                    "Exception while deleting path " + path + ".", e);
        }
    }

    /**
     * This method serializes a given instance of TBase object.
     *
     * @param obj An instance of TBase
     * @return byte array The serialized data.
     * @throws IOException
     */
    static byte[] serialize(TBase obj) throws IOException {
        if (obj == null)
            return new byte[0];
        try {
            TSerializer serializer = new TSerializer(
                    new TBinaryProtocol.Factory());
            byte[] bytes = serializer.serialize(obj);
            return bytes;
        } catch (Exception e) {
            throw new IOException("Serialization error: ", e);
        }
    }


    /**
     * This method deserializes the given byte array into the TBase object.
     *
     * @param obj An instance of TBase
     * @param data Output of deserialization.
     * @throws IOException
     */
    static void deserialize(TBase obj, byte[] data) throws IOException {
        if (data == null || data.length == 0)
            return;
        try {
            TDeserializer deserializer = new TDeserializer(
                    new TBinaryProtocol.Factory());
            deserializer.deserialize(obj, data);
        } catch (Exception e) {
            throw new IOException("Deserialization error: " + e.getMessage(), e);
        }
    }

    private class ZKWatcher implements Watcher {
        public void process(WatchedEvent event) {
            switch (event.getState()) {
                case Expired:
                    LOG.info("The client session has expired. Try opening a new "
                            + "session and connecting again.");
                    zkSession = null;
                    break;
                default:

            }
        }
    }

}
TOP

Related Classes of org.apache.hcatalog.hbase.snapshot.ZKUtil

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.