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