com.alibaba.wasp.fserver.SplitTransaction.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.wasp.fserver.SplitTransaction.java

Source

/**
 * Copyright 2010 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.fserver;

import com.alibaba.wasp.EntityGroupInfo;
import com.alibaba.wasp.EntityGroupTransaction;
import com.alibaba.wasp.Server;
import com.alibaba.wasp.ServerName;
import com.alibaba.wasp.executor.EventHandler;
import com.alibaba.wasp.meta.FMetaEditor;
import com.alibaba.wasp.zookeeper.ZKAssign;
import com.alibaba.wasp.zookeeper.ZKUtil;
import com.alibaba.wasp.zookeeper.ZooKeeperWatcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.CancelableProgressable;
import org.apache.hadoop.hbase.util.HasThread;
import org.apache.hadoop.hbase.util.PairOfSameType;
import org.apache.zookeeper.KeeperException;

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

/**
 * Executes entityGroup split as a "transaction". Call {@link #prepare()} to
 * setup the transaction, {@link #execute(com.alibaba.wasp.Server, FServerServices)} to run the
 * transaction and {@link #rollback(com.alibaba.wasp.Server, FServerServices)} to cleanup if
 * execute fails.
 *
 * <p>
 * Here is an example of how you would use this class:
 *
 * <pre>
 *  SplitTransaction st = new SplitTransaction(this.conf, parent, midKey)
 *  if (!st.prepare()) return;
 *  try {
 *    st.execute(server, services);
 *  } catch (IOException ioe) {
 *    try {
 *      st.rollback(server, services);
 *      return;
 *    } catch (RuntimeException e) {
 *      myAbortable.abort("Failed split, abort");
 *    }
 *  }
 * </Pre>
 * <p>
 * This class is not thread safe. Caller needs ensure split is run by one thread
 * only.
 */
public class SplitTransaction {
    private static final Log LOG = LogFactory.getLog(SplitTransaction.class);
    /*
     * EntityGroup to split
     */
    private final EntityGroup parent;
    private EntityGroupInfo egi_a;
    private EntityGroupInfo egi_b;
    private int znodeVersion = -1;

    /*
     * Row to split around
     */
    private final byte[] splitrow;

    /**
     * Types to add to the transaction journal. Each enum is a step in the split
     * transaction. Used to figure how much we need to rollback.
     */
    enum JournalEntry {
        /**
         * Before creating node in splitting state.
         */
        STARTED_SPLITTING,
        /**
         * Set entityGroup as in transition, set it into SPLITTING state.
         */
        SET_SPLITTING_IN_ZK,
        /**
         * We created the temporary split data directory.
         */
        CREATE_SPLIT_STOREAGE,
        /**
         * Closed the parent entityGroup.
         */
        CLOSED_PARENT_ENTITYGROUP,
        /**
         * The parent has been taken out of the server's online entityGroups list.
         */
        OFFLINED_PARENT,
        /**
         * Started in on creation of the first daughter entityGroup.
         */
        STARTED_ENTITYGROUP_A_CREATION,
        /**
         * Started in on the creation of the second daughter entityGroup.
         */
        STARTED_ENTITYGROUP_B_CREATION,
        /**
         * Point of no return. If we got here, then transaction is not recoverable
         * other than by crashing out the fserver.
         */
        PONR
    }

    /*
     * Journal of how far the split transaction has progressed.
     */
    private final List<JournalEntry> journal = new ArrayList<JournalEntry>();

    private IOException closedByOtherException = new IOException(
            "Failed to close entityGroup: already closed by another thread");

    /**
     * Constructor
     *
     * @param eg
     *          EntityGroup to split
     * @param splitrow
     *          Row to split around
     * @throws java.io.IOException
     */
    // public SplitTransaction(final Configuration conf, final EntityGroup eg,
    // final byte[] splitrow)
    // throws IOException {
    // this(eg, splitrow, conf);
    // }

    public SplitTransaction(final EntityGroup eg, final byte[] splitrow, final Configuration conf)
            throws IOException {
        this.parent = eg;
        this.splitrow = splitrow;
    }

    /**
     * Does checks on split inputs.
     *
     * @return <code>true</code> if the entityGroup is splittable else
     *         <code>false</code> if it is not (e.g. its already closed, etc.).
     */
    public boolean prepare() {
        if (this.parent.isClosed() || this.parent.isClosing()) {
            LOG.info("EntityGroup " + this.parent + " is not splittable," + " opened = "
                    + this.parent.isAvailable());
            return false;
        }
        // Split key can be null if this entityGroup is unsplittable; i.e. has refs.
        if (this.splitrow == null)
            return false;
        EntityGroupInfo egi = this.parent.getEntityGroupInfo();
        // Check splitrow.
        byte[] startKey = egi.getStartKey();
        byte[] endKey = egi.getEndKey();
        if (Bytes.equals(startKey, splitrow) || !this.parent.getEntityGroupInfo().containsRow(splitrow)) {
            LOG.info("Split row is not inside entityGroup key range or is equal to " + "startkey: "
                    + Bytes.toStringBinary(this.splitrow));
            return false;
        }
        long egid = getDaughterEntityGroupIdTimestamp(egi);
        this.egi_a = new EntityGroupInfo(egi.getTableName(), startKey, this.splitrow, false, egid);
        this.egi_b = new EntityGroupInfo(egi.getTableName(), this.splitrow, endKey, false, egid);
        return true;
    }

    /**
     * Calculate daughter entityGroupId to use.
     *
     * @param egi
     *          Parent {@link com.alibaba.wasp.EntityGroupInfo}
     * @return Daughter entityGroup id (timestamp) to use.
     */
    private static long getDaughterEntityGroupIdTimestamp(final EntityGroupInfo egi) {
        long egid = System.currentTimeMillis();
        // EntityGroupId is timestamp. Can't be less than that of parent else will
        // insert
        // at wrong location in .FMETA.
        if (egid < egi.getEntityGroupId()) {
            LOG.warn("Clock skew; parent entityGroup id is " + egi.getEntityGroupId() + " but current time here is "
                    + egid);
            egid = egi.getEntityGroupId() + 1;
        }
        return egid;
    }

    /*
     * Open daughter entityGroup in its own thread. If we fail, abort this hosting
     * server.
     */
    class DaughterOpener extends HasThread {
        private final Server server;
        private final EntityGroup entityGroup;
        private Throwable t = null;

        DaughterOpener(final Server s, final EntityGroup entityGroup) {
            super((s == null ? "null-services" : s.getServerName()) + "-daughterOpener="
                    + entityGroup.getEntityGroupInfo().getEncodedName());
            setDaemon(true);
            this.server = s;
            this.entityGroup = entityGroup;
        }

        /**
         * @return Null if open succeeded else exception that causes us fail open.
         *         Call it after this thread exits else you may get wrong view on
         *         result.
         */
        Throwable getException() {
            return this.t;
        }

        @Override
        public void run() {
            try {
                openDaughterEntityGroup(this.server, entityGroup);
            } catch (Throwable t) {
                this.t = t;
            }
        }
    }

    /**
     * Open daughter entityGroups, add them to online list and update meta.
     *
     * @param server
     * @param daughter
     * @throws java.io.IOException
     * @throws org.apache.zookeeper.KeeperException
     */
    public EntityGroup openDaughterEntityGroup(final Server server, final EntityGroup daughter)
            throws IOException, KeeperException {
        return daughter.openEntityGroup(null);
    }

    static class LoggingProgressable implements CancelableProgressable {
        private final EntityGroupInfo egi;
        private long lastLog = -1;
        private final long interval;

        LoggingProgressable(final EntityGroupInfo egi, final Configuration c) {
            this.egi = egi;
            this.interval = c.getLong("wasp.fserver.split.daughter.open.log.interval", 10000);
        }

        @Override
        public boolean progress() {
            long now = System.currentTimeMillis();
            if (now - lastLog > this.interval) {
                LOG.info("Opening " + this.egi.getEntityGroupNameAsString());
                this.lastLog = now;
            }
            return true;
        }
    }

    /**
     * Run the transaction.
     *
     * @param server
     *          Hosting server instance. Can be null when testing (won't try and
     *          update in zk if a null server)
     * @param services
     *          Used to online/offline entityGroups.
     * @throws java.io.IOException
     *           If thrown, transaction failed. Call
     *           {@link #rollback(com.alibaba.wasp.Server, FServerServices)}
     * @return EntityGroups created
     * @throws java.io.IOException
     * @see #rollback(com.alibaba.wasp.Server, FServerServices)
     */
    public PairOfSameType<EntityGroup> execute(final Server server, final FServerServices services)
            throws IOException {
        PairOfSameType<EntityGroup> entityGroups = createDaughters(server, services);
        openDaughters(server, services, entityGroups.getFirst(), entityGroups.getSecond());
        transitionZKNode(server, services, entityGroups.getFirst(), entityGroups.getSecond());
        return entityGroups;
    }

    /**
     * Prepare the entityGroups.
     *
     * @param server
     *          Hosting server instance. Can be null when testing (won't try and
     *          update in zk if a null server)
     * @param services
     *          Used to online/offline entityGroups.
     * @throws java.io.IOException
     *           If thrown, transaction failed. Call
     *           {@link #rollback(com.alibaba.wasp.Server, FServerServices)}
     * @return EntityGroups created
     */
    PairOfSameType<EntityGroup> createDaughters(final Server server, final FServerServices services)
            throws IOException {
        LOG.info("Starting split of entityGroup " + this.parent.getEntityGroupInfo().toString());
        if ((server != null && server.isStopped()) || (services != null && services.isStopping())) {
            throw new IOException("Server is stopped or stopping");
        }
        assert !this.parent.lock.writeLock()
                .isHeldByCurrentThread() : "Unsafe to hold write lock while performing RPCs";

        // If true, no cluster to write meta edits to or to update znodes in.
        boolean testing = server == null ? true
                : server.getConfiguration().getBoolean("wasp.testing.nocluster", false);

        this.journal.add(JournalEntry.STARTED_SPLITTING);
        // Set ephemeral SPLITTING znode up in zk. Mocked servers sometimes don't
        // have zookeeper so don't do zk stuff if server or zookeeper is null
        if (server != null && server.getZooKeeper() != null) {
            try {
                this.znodeVersion = createNodeSplitting(server.getZooKeeper(), this.parent.getEntityGroupInfo(),
                        server.getServerName());
            } catch (KeeperException e) {
                throw new IOException(
                        "Failed setting SPLITTING znode on " + this.parent.getEntityGroupNameAsString(), e);
            }
        }
        this.journal.add(JournalEntry.SET_SPLITTING_IN_ZK);

        this.journal.add(JournalEntry.CREATE_SPLIT_STOREAGE);

        Exception exceptionToThrow = null;
        boolean result = false;
        try {
            result = this.parent.close(false);
        } catch (Exception e) {
            exceptionToThrow = e;
        }
        if (exceptionToThrow == null && !result) {
            // The entityGroup was closed by a concurrent thread. We can't continue
            // with the split, instead we must just abandon the split. If we
            // reopen or split this could cause problems because the entityGroup has
            // probably already been moved to a different server, or is in the
            // process of moving to a different server.
            exceptionToThrow = closedByOtherException;
        }
        if (exceptionToThrow != closedByOtherException) {
            this.journal.add(JournalEntry.CLOSED_PARENT_ENTITYGROUP);
        }
        if (exceptionToThrow != null) {
            if (exceptionToThrow instanceof IOException)
                throw (IOException) exceptionToThrow;
            throw new IOException(exceptionToThrow);
        }

        if (!testing) {
            services.removeFromOnlineEntityGroups(this.parent.getEntityGroupInfo().getEncodedName());
        }
        this.journal.add(JournalEntry.OFFLINED_PARENT);

        // Log to the journal that we are creating entityGroup A, the first daughter
        // entityGroup. We could fail halfway through. If we do, we could have left
        // stuff in fs that needs cleanup -- a storefile or two. Thats why we
        // add entry to journal BEFORE rather than AFTER the change.
        this.journal.add(JournalEntry.STARTED_ENTITYGROUP_A_CREATION);
        EntityGroup a = createDaughterEntityGroup(this.egi_a, this.parent.services);

        // Ditto
        this.journal.add(JournalEntry.STARTED_ENTITYGROUP_B_CREATION);
        EntityGroup b = createDaughterEntityGroup(this.egi_b, this.parent.services);

        // This is the point of no return. Adding subsequent edits to .META. as we
        // do below when we do the daughter opens adding each to .META. can fail in
        // various interesting ways the most interesting of which is a timeout
        // BUT the edits all go through (See HBASE-3872). IF we reach the PONR
        // then subsequent failures need to crash out this entityGroupserver; the
        // server shutdown processing should be able to fix-up the incomplete split.
        // The offlined parent will have the daughters as extra columns. If
        // we leave the daughter entityGroups in place and do not remove them when
        // we
        // crash out, then they will have their references to the parent in place
        // still and the server shutdown fixup of .META. will point to these
        // entityGroups.
        // We should add PONR JournalEntry before offlineParentInMeta,so even if
        // OfflineParentInMeta timeout,this will cause entityGroupserver exit,and
        // then
        // master ServerShutdownHandler will fix daughter & avoid data loss.
        this.journal.add(JournalEntry.PONR);

        // Edit parent in meta. Offlines parent entityGroup and adds splita and
        // splitb.
        if (!testing) {
            FMetaEditor.offlineParentInMeta(server.getConfiguration(), this.parent.getEntityGroupInfo(),
                    a.getEntityGroupInfo(), b.getEntityGroupInfo());
        }
        return new PairOfSameType<EntityGroup>(a, b);
    }

    /**
     * @param egi
     *          Spec. for daughter entityGroup to open.
     * @return Created daughter EntityGroup.
     * @throws java.io.IOException
     */
    EntityGroup createDaughterEntityGroup(final EntityGroupInfo egi, final FServerServices rsServices)
            throws IOException {
        // Package private so unit tests have access.
        EntityGroup entityGroup = EntityGroup.newEntityGroup(this.parent.getConf(), egi, this.parent.getTableDesc(),
                rsServices);
        entityGroup.readRequestsCount.set(this.parent.getReadRequestsCount() / 2);
        entityGroup.writeRequestsCount.set(this.parent.getWriteRequestsCount() / 2);
        return entityGroup;
    }

    /**
     * Perform time consuming opening of the daughter entityGroups.
     *
     * @param server
     *          Hosting server instance. Can be null when testing (won't try and
     *          update in zk if a null server)
     * @param services
     *          Used to online/offline entityGroups.
     * @param a
     *          first daughter entityGroup
     * @param a
     *          second daughter entityGroup
     * @throws java.io.IOException
     *           If thrown, transaction failed. Call
     *           {@link #rollback(com.alibaba.wasp.Server, FServerServices)}
     */
    void openDaughters(final Server server, final FServerServices services, EntityGroup a, EntityGroup b)
            throws IOException {
        boolean stopped = server != null && server.isStopped();
        boolean stopping = services != null && services.isStopping();
        if (stopped || stopping) {
            LOG.info("Not opening daughters " + b.getEntityGroupInfo().getEntityGroupNameAsString() + " and "
                    + a.getEntityGroupInfo().getEntityGroupNameAsString() + " because stopping=" + stopping
                    + ", stopped=" + stopped);
        } else {
            // Open daughters in parallel.
            DaughterOpener aOpener = new DaughterOpener(server, a);
            DaughterOpener bOpener = new DaughterOpener(server, b);
            aOpener.start();
            bOpener.start();
            try {
                aOpener.join();
                bOpener.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IOException("Interrupted " + e.getMessage());
            }
            if (aOpener.getException() != null) {
                throw new IOException("Failed " + aOpener.getName(), aOpener.getException());
            }
            if (bOpener.getException() != null) {
                throw new IOException("Failed " + bOpener.getName(), bOpener.getException());
            }
            if (services != null) {
                // add 2nd daughter first
                services.postOpenDeployTasks(b, true);
                // Should add it to OnlineEntityGroups
                services.addToOnlineEntityGroups(b);
                services.postOpenDeployTasks(a, true);
                services.addToOnlineEntityGroups(a);
            }
        }
    }

    /**
     * Creates a new ephemeral node in the SPLITTING state for the specified
     * entityGroup. Create it ephemeral in case entityGroupserver dies mid-split.
     *
     * <p>
     * Does not transition nodes from other states. If a node already exists for
     * this entityGroup, a
     * {@link org.apache.zookeeper.KeeperException.NodeExistsException} will be
     * thrown.
     *
     * @param zkw
     *          zk reference
     * @param entityGroup
     *          entityGroup to be created as offline
     * @param serverName
     *          server event originates from
     * @return Version of znode created.
     * @throws org.apache.zookeeper.KeeperException
     * @throws java.io.IOException
     */
    int createNodeSplitting(final ZooKeeperWatcher zkw, final EntityGroupInfo entityGroup,
            final ServerName serverName) throws KeeperException, IOException {
        LOG.debug(
                zkw.prefix("Creating ephemeral node for " + entityGroup.getEncodedName() + " in SPLITTING state"));
        EntityGroupTransaction data = EntityGroupTransaction.createEntityGroupTransition(
                EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLITTING, entityGroup.getEntityGroupName(),
                serverName);
        String node = ZKAssign.getNodeName(zkw, entityGroup.getEncodedName());
        if (!ZKUtil.createEphemeralNodeAndWatch(zkw, node, data.toByteArray())) {
            throw new IOException("Failed create of ephemeral " + node);
        }
        // Transition node from SPLITTING to SPLITTING and pick up version so we
        // can be sure this znode is ours; version is needed deleting.
        return transitionNodeSplitting(zkw, entityGroup, serverName, -1);
    }

    /**
     * Finish off split transaction, transition the zknode
     *
     * @param server
     *          Hosting server instance. Can be null when testing (won't try and
     *          update in zk if a null server)
     * @param services
     *          Used to online/offline entityGroups.
     * @param a
     *          first daughter entityGroup
     * @param a
     *          second daughter entityGroup
     * @throws java.io.IOException
     *           If thrown, transaction failed. Call
     *           {@link #rollback(com.alibaba.wasp.Server, FServerServices)}
     */
    /* package */void transitionZKNode(final Server server, final FServerServices services, EntityGroup a,
            EntityGroup b) throws IOException {
        // Tell master about split by updating zk. If we fail, abort.
        if (server != null && server.getZooKeeper() != null) {
            try {
                this.znodeVersion = transitionNodeSplit(server.getZooKeeper(), parent.getEntityGroupInfo(),
                        a.getEntityGroupInfo(), b.getEntityGroupInfo(), server.getServerName(), this.znodeVersion);

                int spins = 0;
                // Now wait for the master to process the split. We know it's done
                // when the znode is deleted. The reason we keep tickling the znode is
                // that it's possible for the master to miss an event.
                do {
                    if (spins % 10 == 0) {
                        LOG.debug("Still waiting on the master to process the split for "
                                + this.parent.getEntityGroupInfo().getEncodedName());
                    }
                    Thread.sleep(100);
                    // When this returns -1 it means the znode doesn't exist
                    this.znodeVersion = tickleNodeSplit(server.getZooKeeper(), parent.getEntityGroupInfo(),
                            a.getEntityGroupInfo(), b.getEntityGroupInfo(), server.getServerName(),
                            this.znodeVersion);
                    spins++;
                } while (this.znodeVersion != -1 && !server.isStopped() && !services.isStopping());
            } catch (Exception e) {
                if (e instanceof InterruptedException) {
                    Thread.currentThread().interrupt();
                }
                throw new IOException("Failed telling master about split", e);
            }
        }

        // Leaving here, the splitdir with its dross will be in place but since the
        // split was successful, just leave it; it'll be cleaned when parent is
        // deleted and cleaned up.
    }

    /**
     * @param server
     *          Hosting server instance (May be null when testing).
     * @param services
     * @throws java.io.IOException
     *           If thrown, rollback failed. Take drastic action.
     * @return True if we successfully rolled back, false if we got to the point
     *         of no return and so now need to abort the server to minimize
     *         damage.
     */
    public boolean rollback(final Server server, final FServerServices services) throws IOException {
        boolean result = true;
        ListIterator<JournalEntry> iterator = this.journal.listIterator(this.journal.size());
        // Iterate in reverse.
        while (iterator.hasPrevious()) {
            JournalEntry je = iterator.previous();
            switch (je) {

            case STARTED_SPLITTING:
                if (server != null && server.getZooKeeper() != null) {
                    cleanZK(server, this.parent.getEntityGroupInfo(), false);
                }
                break;

            case SET_SPLITTING_IN_ZK:
                if (server != null && server.getZooKeeper() != null) {
                    cleanZK(server, this.parent.getEntityGroupInfo(), true);
                }
                break;

            case CREATE_SPLIT_STOREAGE:
                this.parent.writestate.writesEnabled = true;
                break;

            case CLOSED_PARENT_ENTITYGROUP:
                try {
                    this.parent.initialize();
                } catch (IOException e) {
                    LOG.error("Failed rollbacking CLOSED_PARENT_ENTITYGROUP of entityGroup "
                            + this.parent.getEntityGroupNameAsString(), e);
                    throw new RuntimeException(e);
                }
                break;

            case STARTED_ENTITYGROUP_A_CREATION:
                break;

            case STARTED_ENTITYGROUP_B_CREATION:
                break;

            case OFFLINED_PARENT:
                if (services != null)
                    services.addToOnlineEntityGroups(this.parent);
                break;

            case PONR:
                // We got to the point-of-no-return so we need to just abort. Return
                // immediately. Do not clean up created daughter entityGroups. They need
                // to be in place so we don't delete the parent entityGroup mistakenly.
                // See HBASE-3872.
                return false;

            default:
                throw new RuntimeException("Unhandled journal entry: " + je);
            }
        }
        return result;
    }

    private static void cleanZK(final Server server, final EntityGroupInfo egi, boolean abort) {
        try {
            // Only delete if its in expected state; could have been hijacked.
            ZKAssign.deleteNode(server.getZooKeeper(), egi.getEncodedName(),
                    EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLITTING);
        } catch (KeeperException.NoNodeException nn) {
            if (abort) {
                server.abort("Failed cleanup of " + egi.getEntityGroupNameAsString(), nn);
            }
        } catch (KeeperException e) {
            server.abort("Failed cleanup of " + egi.getEntityGroupNameAsString(), e);
        }
    }

    /**
     * Transitions an existing node for the specified entityGroup which is
     * currently in the SPLITTING state to be in the SPLIT state. Converts the
     * ephemeral SPLITTING znode to an ephemeral SPLIT node. Master cleans up
     * SPLIT znode when it reads it (or if we crash, zk will clean it up).
     *
     * <p>
     * Does not transition nodes from other states. If for some reason the node
     * could not be transitioned, the method returns -1. If the transition is
     * successful, the version of the node after transition is returned.
     *
     * <p>
     * This method can fail and return false for three different reasons:
     * <ul>
     * <li>Node for this entityGroup does not exist</li>
     * <li>Node for this entityGroup is not in SPLITTING state</li>
     * <li>After verifying SPLITTING state, update fails because of wrong version
     * (this should never actually happen since an RS only does this transition
     * following a transition to SPLITTING. if two RS are conflicting, one would
     * fail the original transition to SPLITTING and not this transition)</li>
     * </ul>
     *
     * <p>
     * Does not set any watches.
     *
     * <p>
     * This method should only be used by a FServer when completing the open of a
     * entityGroup.
     *
     * @param zkw
     *          zk reference
     * @param parent
     *          entityGroup to be transitioned to opened
     * @param a
     *          Daughter a of split
     * @param b
     *          Daughter b of split
     * @param serverName
     *          server event originates from
     * @return version of node after transition, -1 if unsuccessful transition
     * @throws org.apache.zookeeper.KeeperException
     *           if unexpected zookeeper exception
     * @throws java.io.IOException
     */
    private static int transitionNodeSplit(ZooKeeperWatcher zkw, EntityGroupInfo parent, EntityGroupInfo a,
            EntityGroupInfo b, ServerName serverName, final int znodeVersion) throws KeeperException, IOException {
        byte[] payload = EntityGroupInfo.toDelimitedByteArray(a, b);
        return ZKAssign.transitionNode(zkw, parent, serverName,
                EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLITTING,
                EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLIT, znodeVersion, payload);
    }

    EntityGroupInfo getFirstDaughter() {
        return egi_a;
    }

    EntityGroupInfo getSecondDaughter() {
        return egi_b;
    }

    /**
     *
     * @param zkw
     *          zk reference
     * @param parent
     *          entityGroup to be transitioned to splitting
     * @param serverName
     *          server event originates from
     * @param version
     *          znode version
     * @return version of node after transition, -1 if unsuccessful transition
     * @throws org.apache.zookeeper.KeeperException
     * @throws java.io.IOException
     */
    int transitionNodeSplitting(final ZooKeeperWatcher zkw, final EntityGroupInfo parent,
            final ServerName serverName, final int version) throws KeeperException, IOException {
        return ZKAssign.transitionNode(zkw, parent, serverName,
                EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLITTING,
                EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLITTING, version);
    }

    private static int tickleNodeSplit(ZooKeeperWatcher zkw, EntityGroupInfo parent, EntityGroupInfo a,
            EntityGroupInfo b, ServerName serverName, final int znodeVersion) throws KeeperException, IOException {
        byte[] payload = EntityGroupInfo.toDelimitedByteArray(a, b);
        return ZKAssign.transitionNode(zkw, parent, serverName, EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLIT,
                EventHandler.EventType.FSERVER_ZK_ENTITYGROUP_SPLIT, znodeVersion, payload);
    }
}