You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by ec...@apache.org on 2013/02/25 23:50:29 UTC

svn commit: r1449950 [5/35] - in /hbase/trunk: ./ hbase-client/ hbase-client/src/ hbase-client/src/main/ hbase-client/src/main/java/ hbase-client/src/main/java/org/ hbase-client/src/main/java/org/apache/ hbase-client/src/main/java/org/apache/hadoop/ hb...

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerName.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerName.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerName.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerName.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,355 @@
+/**
+ *
+ * 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.hadoop.hbase;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.hbase.exceptions.DeserializationException;
+import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos.RootRegionServer;
+import org.apache.hadoop.hbase.util.Addressing;
+import org.apache.hadoop.hbase.util.Bytes;
+
+import java.util.Collection;
+import java.util.regex.Pattern;
+
+/**
+ * Instance of an HBase ServerName.
+ * A server name is used uniquely identifying a server instance and is made
+ * of the combination of hostname, port, and startcode. The startcode
+ * distingushes restarted servers on same hostname and port (startcode is
+ * usually timestamp of server startup). The {@link #toString()} format of
+ * ServerName is safe to use in the  filesystem and as znode name up in
+ * ZooKeeper.  Its format is:
+ * <code>&lt;hostname> '{@link #SERVERNAME_SEPARATOR}' &lt;port> '{@link #SERVERNAME_SEPARATOR}' &lt;startcode></code>.
+ * For example, if hostname is <code>example.org</code>, port is <code>1234</code>,
+ * and the startcode for the regionserver is <code>1212121212</code>, then
+ * the {@link #toString()} would be <code>example.org,1234,1212121212</code>.
+ * 
+ * <p>You can obtain a versioned serialized form of this class by calling
+ * {@link #getVersionedBytes()}.  To deserialize, call {@link #parseVersionedServerName(byte[])}
+ * 
+ * <p>Immutable.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public class ServerName implements Comparable<ServerName> {
+  /**
+   * Version for this class.
+   * Its a short rather than a byte so I can for sure distinguish between this
+   * version of this class and the version previous to this which did not have
+   * a version.
+   */
+  private static final short VERSION = 0;
+  static final byte [] VERSION_BYTES = Bytes.toBytes(VERSION);
+
+  /**
+   * What to use if no startcode supplied.
+   */
+  public static final int NON_STARTCODE = -1;
+
+  /**
+   * This character is used as separator between server hostname, port and
+   * startcode.
+   */
+  public static final String SERVERNAME_SEPARATOR = ",";
+
+  public static final Pattern SERVERNAME_PATTERN =
+    Pattern.compile("[^" + SERVERNAME_SEPARATOR + "]+" +
+      SERVERNAME_SEPARATOR + Addressing.VALID_PORT_REGEX +
+      SERVERNAME_SEPARATOR + Addressing.VALID_PORT_REGEX + "$");
+
+  /**
+   * What to use if server name is unknown.
+   */
+  public static final String UNKNOWN_SERVERNAME = "#unknown#";
+
+  private final String servername;
+  private final String hostname;
+  private final int port;
+  private final long startcode;
+
+  /**
+   * Cached versioned bytes of this ServerName instance.
+   * @see #getVersionedBytes()
+   */
+  private byte [] bytes;
+
+  public ServerName(final String hostname, final int port, final long startcode) {
+    this.hostname = hostname;
+    this.port = port;
+    this.startcode = startcode;
+    this.servername = getServerName(hostname, port, startcode);
+  }
+
+  public ServerName(final String serverName) {
+    this(parseHostname(serverName), parsePort(serverName),
+      parseStartcode(serverName));
+  }
+
+  public ServerName(final String hostAndPort, final long startCode) {
+    this(Addressing.parseHostname(hostAndPort),
+      Addressing.parsePort(hostAndPort), startCode);
+  }
+
+  public static String parseHostname(final String serverName) {
+    if (serverName == null || serverName.length() <= 0) {
+      throw new IllegalArgumentException("Passed hostname is null or empty");
+    }
+    int index = serverName.indexOf(SERVERNAME_SEPARATOR);
+    return serverName.substring(0, index);
+  }
+
+  public static int parsePort(final String serverName) {
+    String [] split = serverName.split(SERVERNAME_SEPARATOR);
+    return Integer.parseInt(split[1]);
+  }
+
+  public static long parseStartcode(final String serverName) {
+    int index = serverName.lastIndexOf(SERVERNAME_SEPARATOR);
+    return Long.parseLong(serverName.substring(index + 1));
+  }
+
+  @Override
+  public String toString() {
+    return getServerName();
+  }
+
+  /**
+   * @return {@link #getServerName()} as bytes with a short-sized prefix with
+   * the ServerName#VERSION of this class.
+   */
+  public synchronized byte [] getVersionedBytes() {
+    if (this.bytes == null) {
+      this.bytes = Bytes.add(VERSION_BYTES, Bytes.toBytes(getServerName()));
+    }
+    return this.bytes;
+  }
+
+  public String getServerName() {
+    return servername;
+  }
+
+  public String getHostname() {
+    return hostname;
+  }
+
+  public int getPort() {
+    return port;
+  }
+
+  public long getStartcode() {
+    return startcode;
+  }
+
+  /**
+   * @param hostName
+   * @param port
+   * @param startcode
+   * @return Server name made of the concatenation of hostname, port and
+   * startcode formatted as <code>&lt;hostname> ',' &lt;port> ',' &lt;startcode></code>
+   */
+  public static String getServerName(String hostName, int port, long startcode) {
+    final StringBuilder name = new StringBuilder(hostName.length() + 1 + 5 + 1 + 13);
+    name.append(hostName);
+    name.append(SERVERNAME_SEPARATOR);
+    name.append(port);
+    name.append(SERVERNAME_SEPARATOR);
+    name.append(startcode);
+    return name.toString();
+  }
+
+  /**
+   * @param hostAndPort String in form of &lt;hostname> ':' &lt;port>
+   * @param startcode
+   * @return Server name made of the concatenation of hostname, port and
+   * startcode formatted as <code>&lt;hostname> ',' &lt;port> ',' &lt;startcode></code>
+   */
+  public static String getServerName(final String hostAndPort,
+      final long startcode) {
+    int index = hostAndPort.indexOf(":");
+    if (index <= 0) throw new IllegalArgumentException("Expected <hostname> ':' <port>");
+    return getServerName(hostAndPort.substring(0, index),
+      Integer.parseInt(hostAndPort.substring(index + 1)), startcode);
+  }
+
+  /**
+   * @return Hostname and port formatted as described at
+   * {@link Addressing#createHostAndPortStr(String, int)}
+   */
+  public String getHostAndPort() {
+    return Addressing.createHostAndPortStr(this.hostname, this.port);
+  }
+
+  /**
+   * @param serverName ServerName in form specified by {@link #getServerName()}
+   * @return The server start code parsed from <code>servername</code>
+   */
+  public static long getServerStartcodeFromServerName(final String serverName) {
+    int index = serverName.lastIndexOf(SERVERNAME_SEPARATOR);
+    return Long.parseLong(serverName.substring(index + 1));
+  }
+
+  /**
+   * Utility method to excise the start code from a server name
+   * @param inServerName full server name
+   * @return server name less its start code
+   */
+  public static String getServerNameLessStartCode(String inServerName) {
+    if (inServerName != null && inServerName.length() > 0) {
+      int index = inServerName.lastIndexOf(SERVERNAME_SEPARATOR);
+      if (index > 0) {
+        return inServerName.substring(0, index);
+      }
+    }
+    return inServerName;
+  }
+
+  @Override
+  public int compareTo(ServerName other) {
+    int compare = this.getHostname().toLowerCase().
+      compareTo(other.getHostname().toLowerCase());
+    if (compare != 0) return compare;
+    compare = this.getPort() - other.getPort();
+    if (compare != 0) return compare;
+    return (int)(this.getStartcode() - other.getStartcode());
+  }
+
+  @Override
+  public int hashCode() {
+    return getServerName().hashCode();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null) return false;
+    if (!(o instanceof ServerName)) return false;
+    return this.compareTo((ServerName)o) == 0;
+  }
+
+
+  /**
+   * @return ServerName with matching hostname and port.
+   */
+  public static ServerName findServerWithSameHostnamePort(final Collection<ServerName> names,
+      final ServerName serverName) {
+    for (ServerName sn: names) {
+      if (isSameHostnameAndPort(serverName, sn)) return sn;
+    }
+    return null;
+  }
+
+  /**
+   * @param left
+   * @param right
+   * @return True if <code>other</code> has same hostname and port.
+   */
+  public static boolean isSameHostnameAndPort(final ServerName left,
+      final ServerName right) {
+    if (left == null) return false;
+    if (right == null) return false;
+    return left.getHostname().equals(right.getHostname()) &&
+      left.getPort() == right.getPort();
+  }
+
+  /**
+   * Use this method instantiating a {@link ServerName} from bytes
+   * gotten from a call to {@link #getVersionedBytes()}.  Will take care of the
+   * case where bytes were written by an earlier version of hbase.
+   * @param versionedBytes Pass bytes gotten from a call to {@link #getVersionedBytes()}
+   * @return A ServerName instance.
+   * @see #getVersionedBytes()
+   */
+  public static ServerName parseVersionedServerName(final byte [] versionedBytes) {
+    // Version is a short.
+    short version = Bytes.toShort(versionedBytes);
+    if (version == VERSION) {
+      int length = versionedBytes.length - Bytes.SIZEOF_SHORT;
+      return new ServerName(Bytes.toString(versionedBytes, Bytes.SIZEOF_SHORT, length));
+    }
+    // Presume the bytes were written with an old version of hbase and that the
+    // bytes are actually a String of the form "'<hostname>' ':' '<port>'".
+    return new ServerName(Bytes.toString(versionedBytes), NON_STARTCODE);
+  }
+
+  /**
+   * @param str Either an instance of {@link ServerName#toString()} or a
+   * "'<hostname>' ':' '<port>'".
+   * @return A ServerName instance.
+   */
+  public static ServerName parseServerName(final String str) {
+    return SERVERNAME_PATTERN.matcher(str).matches()? new ServerName(str):
+      new ServerName(str, NON_STARTCODE);
+  }
+
+
+  /**
+   * @return true if the String follows the pattern of {@link ServerName#toString()}, false
+   *  otherwise.
+   */
+  public static boolean isFullServerName(final String str){
+    if (str == null ||str.isEmpty()) return false;
+    return SERVERNAME_PATTERN.matcher(str).matches();
+  }
+
+  /**
+   * Get a ServerName from the passed in data bytes.
+   * @param data Data with a serialize server name in it; can handle the old style
+   * servername where servername was host and port.  Works too with data that
+   * begins w/ the pb 'PBUF' magic and that is then followed by a protobuf that
+   * has a serialized {@link ServerName} in it.
+   * @return Returns null if <code>data</code> is null else converts passed data
+   * to a ServerName instance.
+   * @throws DeserializationException 
+   */
+  public static ServerName parseFrom(final byte [] data) throws DeserializationException {
+    if (data == null || data.length <= 0) return null;
+    if (ProtobufUtil.isPBMagicPrefix(data)) {
+      int prefixLen = ProtobufUtil.lengthOfPBMagic();
+      try {
+        RootRegionServer rss =
+          RootRegionServer.newBuilder().mergeFrom(data, prefixLen, data.length - prefixLen).build();
+        org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.ServerName sn = rss.getServer();
+        return new ServerName(sn.getHostName(), sn.getPort(), sn.getStartCode());
+      } catch (InvalidProtocolBufferException e) {
+        // A failed parse of the znode is pretty catastrophic. Rather than loop
+        // retrying hoping the bad bytes will changes, and rather than change
+        // the signature on this method to add an IOE which will send ripples all
+        // over the code base, throw a RuntimeException.  This should "never" happen.
+        // Fail fast if it does.
+        throw new DeserializationException(e);
+      }
+    }
+    // The str returned could be old style -- pre hbase-1502 -- which was
+    // hostname and port seperated by a colon rather than hostname, port and
+    // startcode delimited by a ','.
+    String str = Bytes.toString(data);
+    int index = str.indexOf(ServerName.SERVERNAME_SEPARATOR);
+    if (index != -1) {
+      // Presume its ServerName serialized with versioned bytes.
+      return ServerName.parseVersionedServerName(data);
+    }
+    // Presume it a hostname:port format.
+    String hostname = Addressing.parseHostname(str);
+    int port = Addressing.parsePort(str);
+    return new ServerName(hostname, port, -1L);
+  }
+}

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/Stoppable.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/Stoppable.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/Stoppable.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/Stoppable.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,38 @@
+/**
+ *
+ * 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.hadoop.hbase;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+
+/**
+ * Implementers are Stoppable.
+ */
+@InterfaceAudience.Private
+public interface Stoppable {
+  /**
+   * Stop this service.
+   * @param why Why we're stopping.
+   */
+  public void stop(String why);
+
+  /**
+   * @return True if {@link #stop(String)} has been closed.
+   */
+  public boolean isStopped();
+}

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/CatalogTracker.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/CatalogTracker.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/CatalogTracker.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/CatalogTracker.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,640 @@
+/**
+ * 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.hadoop.hbase.catalog;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.Abortable;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.client.AdminProtocol;
+import org.apache.hadoop.hbase.client.HConnection;
+import org.apache.hadoop.hbase.client.HConnectionManager;
+import org.apache.hadoop.hbase.client.HTable;
+import org.apache.hadoop.hbase.client.RetriesExhaustedException;
+import org.apache.hadoop.hbase.exceptions.NotAllMetaRegionsOnlineException;
+import org.apache.hadoop.hbase.exceptions.ServerNotRunningYetException;
+import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.zookeeper.MetaNodeTracker;
+import org.apache.hadoop.hbase.zookeeper.RootRegionTracker;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+import org.apache.hadoop.ipc.RemoteException;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.NoRouteToHostException;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tracks the availability of the catalog tables <code>-ROOT-</code> and
+ * <code>.META.</code>.
+ * 
+ * This class is "read-only" in that the locations of the catalog tables cannot
+ * be explicitly set.  Instead, ZooKeeper is used to learn of the availability
+ * and location of <code>-ROOT-</code>.  <code>-ROOT-</code> is used to learn of
+ * the location of <code>.META.</code>  If not available in <code>-ROOT-</code>,
+ * ZooKeeper is used to monitor for a new location of <code>.META.</code>.
+ *
+ * <p>Call {@link #start()} to start up operation.  Call {@link #stop()}} to
+ * interrupt waits and close up shop.
+ */
+@InterfaceAudience.Private
+public class CatalogTracker {
+  // TODO: This class needs a rethink.  The original intent was that it would be
+  // the one-stop-shop for root and meta locations and that it would get this
+  // info from reading and watching zk state.  The class was to be used by
+  // servers when they needed to know of root and meta movement but also by
+  // client-side (inside in HTable) so rather than figure root and meta
+  // locations on fault, the client would instead get notifications out of zk.
+  // 
+  // But this original intent is frustrated by the fact that this class has to
+  // read an hbase table, the -ROOT- table, to figure out the .META. region
+  // location which means we depend on an HConnection.  HConnection will do
+  // retrying but also, it has its own mechanism for finding root and meta
+  // locations (and for 'verifying'; it tries the location and if it fails, does
+  // new lookup, etc.).  So, at least for now, HConnection (or HTable) can't
+  // have a CT since CT needs a HConnection (Even then, do want HT to have a CT?
+  // For HT keep up a session with ZK?  Rather, shouldn't we do like asynchbase
+  // where we'd open a connection to zk, read what we need then let the
+  // connection go?).  The 'fix' is make it so both root and meta addresses
+  // are wholey up in zk -- not in zk (root) -- and in an hbase table (meta).
+  //
+  // But even then, this class does 'verification' of the location and it does
+  // this by making a call over an HConnection (which will do its own root
+  // and meta lookups).  Isn't this verification 'useless' since when we
+  // return, whatever is dependent on the result of this call then needs to
+  // use HConnection; what we have verified may change in meantime (HConnection
+  // uses the CT primitives, the root and meta trackers finding root locations).
+  //
+  // When meta is moved to zk, this class may make more sense.  In the
+  // meantime, it does not cohere.  It should just watch meta and root and not
+  // NOT do verification -- let that be out in HConnection since its going to
+  // be done there ultimately anyways.
+  //
+  // This class has spread throughout the codebase.  It needs to be reigned in.
+  // This class should be used server-side only, even if we move meta location
+  // up into zk.  Currently its used over in the client package. Its used in
+  // MetaReader and MetaEditor classes usually just to get the Configuration
+  // its using (It does this indirectly by asking its HConnection for its
+  // Configuration and even then this is just used to get an HConnection out on
+  // the other end). I made https://issues.apache.org/jira/browse/HBASE-4495 for
+  // doing CT fixup. St.Ack 09/30/2011.
+  //
+
+  // TODO: Timeouts have never been as advertised in here and its worse now
+  // with retries; i.e. the HConnection retries and pause goes ahead whatever
+  // the passed timeout is.  Fix.
+  private static final Log LOG = LogFactory.getLog(CatalogTracker.class);
+  private final HConnection connection;
+  private final ZooKeeperWatcher zookeeper;
+  private final RootRegionTracker rootRegionTracker;
+  private final MetaNodeTracker metaNodeTracker;
+  private final AtomicBoolean metaAvailable = new AtomicBoolean(false);
+  private boolean instantiatedzkw = false;
+  private Abortable abortable;
+
+  /*
+   * Do not clear this address once set.  Its needed when we do
+   * server shutdown processing -- we need to know who had .META. last.  If you
+   * want to know if the address is good, rely on {@link #metaAvailable} value.
+   */
+  private ServerName metaLocation;
+
+  private boolean stopped = false;
+
+  static final byte [] ROOT_REGION_NAME =
+    HRegionInfo.ROOT_REGIONINFO.getRegionName();
+  static final byte [] META_REGION_NAME =
+    HRegionInfo.FIRST_META_REGIONINFO.getRegionName();
+
+  /**
+   * Constructs a catalog tracker. Find current state of catalog tables.
+   * Begin active tracking by executing {@link #start()} post construction. Does
+   * not timeout.
+   *
+   * @param conf
+   *          the {@link Configuration} from which a {@link HConnection} will be
+   *          obtained; if problem, this connections
+   *          {@link HConnection#abort(String, Throwable)} will be called.
+   * @throws IOException
+   */
+  public CatalogTracker(final Configuration conf) throws IOException {
+    this(null, conf, null);
+  }
+
+  /**
+   * Constructs the catalog tracker.  Find current state of catalog tables.
+   * Begin active tracking by executing {@link #start()} post construction.
+   * Does not timeout.
+   * @param zk If zk is null, we'll create an instance (and shut it down
+   * when {@link #stop()} is called) else we'll use what is passed.
+   * @param conf
+   * @param abortable If fatal exception we'll call abort on this.  May be null.
+   * If it is we'll use the Connection associated with the passed
+   * {@link Configuration} as our Abortable.
+   * @throws IOException 
+   */
+  public CatalogTracker(final ZooKeeperWatcher zk, final Configuration conf,
+      Abortable abortable)
+  throws IOException {
+    this(zk, conf, HConnectionManager.getConnection(conf), abortable);
+  }
+
+  public CatalogTracker(final ZooKeeperWatcher zk, final Configuration conf,
+      HConnection connection, Abortable abortable)
+  throws IOException {
+    this.connection = connection;
+    if (abortable == null) {
+      // A connection is abortable.
+      this.abortable = this.connection;
+    }
+    Abortable throwableAborter = new Abortable() {
+
+      @Override
+      public void abort(String why, Throwable e) {
+        throw new RuntimeException(why, e);
+      }
+
+      @Override
+      public boolean isAborted() {
+        return true;
+      }
+
+    };
+    if (zk == null) {
+      // Create our own.  Set flag so we tear it down on stop.
+      this.zookeeper =
+        new ZooKeeperWatcher(conf, "catalogtracker-on-" + connection.toString(),
+          abortable);
+      instantiatedzkw = true;
+    } else {
+      this.zookeeper = zk;
+    }
+    this.rootRegionTracker = new RootRegionTracker(zookeeper, throwableAborter);
+    final CatalogTracker ct = this;
+    // Override nodeDeleted so we get notified when meta node deleted
+    this.metaNodeTracker = new MetaNodeTracker(zookeeper, throwableAborter) {
+      public void nodeDeleted(String path) {
+        if (!path.equals(node)) return;
+        ct.resetMetaLocation();
+      }
+    };
+  }
+
+  /**
+   * Starts the catalog tracker.
+   * Determines current availability of catalog tables and ensures all further
+   * transitions of either region are tracked.
+   * @throws IOException
+   * @throws InterruptedException 
+   */
+  public void start() throws IOException, InterruptedException {
+    LOG.debug("Starting catalog tracker " + this);
+    try {
+      this.rootRegionTracker.start();
+      this.metaNodeTracker.start();
+    } catch (RuntimeException e) {
+      Throwable t = e.getCause();
+      this.abortable.abort(e.getMessage(), t);
+      throw new IOException("Attempt to start root/meta tracker failed.", t);
+    }
+  }
+
+  /**
+   * Stop working.
+   * Interrupts any ongoing waits.
+   */
+  public void stop() {
+    if (!this.stopped) {
+      LOG.debug("Stopping catalog tracker " + this);
+      this.stopped = true;
+      this.rootRegionTracker.stop();
+      this.metaNodeTracker.stop();
+      try {
+        if (this.connection != null) {
+          this.connection.close();
+        }
+      } catch (IOException e) {
+        // Although the {@link Closeable} interface throws an {@link
+        // IOException}, in reality, the implementation would never do that.
+        LOG.error("Attempt to close catalog tracker's connection failed.", e);
+      }
+      if (this.instantiatedzkw) {
+        this.zookeeper.close();
+      }
+      // Call this and it will interrupt any ongoing waits on meta.
+      synchronized (this.metaAvailable) {
+        this.metaAvailable.notifyAll();
+      }
+    }
+  }
+
+  /**
+   * Gets the current location for <code>-ROOT-</code> or null if location is
+   * not currently available.
+   * @return {@link ServerName} for server hosting <code>-ROOT-</code> or null
+   * if none available
+   * @throws InterruptedException 
+   */
+  public ServerName getRootLocation() throws InterruptedException {
+    return this.rootRegionTracker.getRootRegionLocation();
+  }
+
+  /**
+   * @return {@link ServerName} for server hosting <code>.META.</code> or null
+   * if none available
+   */
+  public ServerName getMetaLocation() {
+    return this.metaLocation;
+  }
+
+  /**
+   * Method used by master on startup trying to figure state of cluster.
+   * Returns the current meta location unless its null.  In this latter case,
+   * it has not yet been set so go check whats up in <code>-ROOT-</code> and
+   * return that.
+   * @return {@link ServerName} for server hosting <code>.META.</code> or if null,
+   * we'll read the location that is up in <code>-ROOT-</code> table (which
+   * could be null or just plain stale).
+   * @throws IOException
+   */
+  public ServerName getMetaLocationOrReadLocationFromRoot() throws IOException {
+    ServerName sn = getMetaLocation();
+    return sn != null? sn: MetaReader.getMetaRegionLocation(this);
+  }
+
+  /**
+   * Gets the current location for <code>-ROOT-</code> if available and waits
+   * for up to the specified timeout if not immediately available.  Returns null
+   * if the timeout elapses before root is available.
+   * @param timeout maximum time to wait for root availability, in milliseconds
+   * @return {@link ServerName} for server hosting <code>-ROOT-</code> or null
+   * if none available
+   * @throws InterruptedException if interrupted while waiting
+   * @throws NotAllMetaRegionsOnlineException if root not available before
+   * timeout
+   */
+  public ServerName waitForRoot(final long timeout)
+  throws InterruptedException, NotAllMetaRegionsOnlineException {
+    ServerName sn = rootRegionTracker.waitRootRegionLocation(timeout);
+    if (sn == null) {
+      throw new NotAllMetaRegionsOnlineException("Timed out; " + timeout + "ms");
+    }
+    return sn;
+  }
+
+  /**
+   * Gets a connection to the server hosting root, as reported by ZooKeeper,
+   * waiting up to the specified timeout for availability.
+   * @param timeout How long to wait on root location
+   * @see #waitForRoot(long) for additional information
+   * @return connection to server hosting root
+   * @throws InterruptedException
+   * @throws NotAllMetaRegionsOnlineException if timed out waiting
+   * @throws IOException
+   * @deprecated Use #getRootServerConnection(long)
+   */
+  public AdminProtocol waitForRootServerConnection(long timeout)
+  throws InterruptedException, NotAllMetaRegionsOnlineException, IOException {
+    return getRootServerConnection(timeout);
+  }
+
+  /**
+   * Gets a connection to the server hosting root, as reported by ZooKeeper,
+   * waiting up to the specified timeout for availability.
+   * <p>WARNING: Does not retry.  Use an {@link HTable} instead.
+   * @param timeout How long to wait on root location
+   * @see #waitForRoot(long) for additional information
+   * @return connection to server hosting root
+   * @throws InterruptedException
+   * @throws NotAllMetaRegionsOnlineException if timed out waiting
+   * @throws IOException
+   */
+  AdminProtocol getRootServerConnection(long timeout)
+  throws InterruptedException, NotAllMetaRegionsOnlineException, IOException {
+    return getCachedConnection(waitForRoot(timeout));
+  }
+
+  /**
+   * Gets a connection to the server currently hosting <code>.META.</code> or
+   * null if location is not currently available.
+   * <p>
+   * If a location is known, a connection to the cached location is returned.
+   * If refresh is true, the cached connection is verified first before
+   * returning.  If the connection is not valid, it is reset and rechecked.
+   * <p>
+   * If no location for meta is currently known, method checks ROOT for a new
+   * location, verifies META is currently there, and returns a cached connection
+   * to the server hosting META.
+   *
+   * @return connection to server hosting meta, null if location not available
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  private AdminProtocol getMetaServerConnection()
+  throws IOException, InterruptedException {
+    synchronized (metaAvailable) {
+      if (metaAvailable.get()) {
+        AdminProtocol current = getCachedConnection(this.metaLocation);
+        // If we are to refresh, verify we have a good connection by making
+        // an invocation on it.
+        if (verifyRegionLocation(current, this.metaLocation, META_REGION_NAME)) {
+          return current;
+        }
+        resetMetaLocation();
+      }
+      // We got here because there is no meta available or because whats
+      // available is bad.
+
+      // Now read the current .META. content from -ROOT-.  Note: This goes via
+      // an HConnection.  It has its own way of figuring root and meta locations
+      // which we have to wait on.
+      ServerName newLocation = MetaReader.getMetaRegionLocation(this);
+      if (newLocation == null) return null;
+
+      AdminProtocol newConnection = getCachedConnection(newLocation);
+      if (verifyRegionLocation(newConnection, newLocation, META_REGION_NAME)) {
+        setMetaLocation(newLocation);
+        return newConnection;
+      } else {
+        if (LOG.isTraceEnabled()) {
+          LOG.trace("New .META. server: " + newLocation + " isn't valid." +
+            " Cached .META. server: " + this.metaLocation);
+        }
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Waits indefinitely for availability of <code>.META.</code>.  Used during
+   * cluster startup.  Does not verify meta, just that something has been
+   * set up in zk.
+   * @see #waitForMeta(long)
+   * @throws InterruptedException if interrupted while waiting
+   */
+  public void waitForMeta() throws InterruptedException {
+    while (!this.stopped) {
+      try {
+        if (waitForMeta(100) != null) break;
+      } catch (NotAllMetaRegionsOnlineException e) {
+        if (LOG.isTraceEnabled()) {
+          LOG.info(".META. still not available, sleeping and retrying." +
+          " Reason: " + e.getMessage());
+        }
+      } catch (IOException e) {
+        LOG.info("Retrying", e);
+      }
+    }
+  }
+
+  /**
+   * Gets the current location for <code>.META.</code> if available and waits
+   * for up to the specified timeout if not immediately available.  Throws an
+   * exception if timed out waiting.  This method differs from {@link #waitForMeta()}
+   * in that it will go ahead and verify the location gotten from ZooKeeper and
+   * -ROOT- region by trying to use returned connection.
+   * @param timeout maximum time to wait for meta availability, in milliseconds
+   * @return {@link ServerName} for server hosting <code>.META.</code> or null
+   * if none available
+   * @throws InterruptedException if interrupted while waiting
+   * @throws IOException unexpected exception connecting to meta server
+   * @throws NotAllMetaRegionsOnlineException if meta not available before
+   * timeout
+   */
+  public ServerName waitForMeta(long timeout)
+  throws InterruptedException, IOException, NotAllMetaRegionsOnlineException {
+    long stop = timeout == 0 ? Long.MAX_VALUE : System.currentTimeMillis() + timeout;
+    long waitTime = Math.min(50, timeout);
+    synchronized (metaAvailable) {
+      while(!stopped && System.currentTimeMillis() < stop) {
+        if (getMetaServerConnection() != null) {
+          return metaLocation;
+        }
+        // perhaps -ROOT- region isn't available, let us wait a bit and retry.
+        metaAvailable.wait(waitTime);
+      }
+      if (getMetaServerConnection() == null) {
+        throw new NotAllMetaRegionsOnlineException("Timed out (" + timeout + "ms)");
+      }
+      return metaLocation;
+    }
+  }
+
+  /**
+   * Gets a connection to the server hosting meta, as reported by ZooKeeper,
+   * waiting up to the specified timeout for availability.
+   * @see #waitForMeta(long) for additional information
+   * @return connection to server hosting meta
+   * @throws InterruptedException
+   * @throws NotAllMetaRegionsOnlineException if timed out waiting
+   * @throws IOException
+   * @deprecated Does not retry; use an HTable instance instead.
+   */
+  public AdminProtocol waitForMetaServerConnection(long timeout)
+  throws InterruptedException, NotAllMetaRegionsOnlineException, IOException {
+    return getCachedConnection(waitForMeta(timeout));
+  }
+
+  /**
+   * Called when we figure current meta is off (called from zk callback).
+   */
+  public void resetMetaLocation() {
+    LOG.debug("Current cached META location, " + metaLocation +
+      ", is not valid, resetting");
+    synchronized(this.metaAvailable) {
+      this.metaAvailable.set(false);
+      this.metaAvailable.notifyAll();
+    }
+  }
+
+  /**
+   * @param metaLocation
+   */
+  void setMetaLocation(final ServerName metaLocation) {
+    LOG.debug("Set new cached META location: " + metaLocation);
+    synchronized (this.metaAvailable) {
+      this.metaLocation = metaLocation;
+      this.metaAvailable.set(true);
+      // no synchronization because these are private and already under lock
+      this.metaAvailable.notifyAll();
+    }
+  }
+
+  /**
+   * @param sn ServerName to get a connection against.
+   * @return The AdminProtocol we got when we connected to <code>sn</code>
+   * May have come from cache, may not be good, may have been setup by this
+   * invocation, or may be null.
+   * @throws IOException
+   */
+  private AdminProtocol getCachedConnection(ServerName sn)
+  throws IOException {
+    if (sn == null) {
+      return null;
+    }
+    AdminProtocol protocol = null;
+    try {
+      protocol = connection.getAdmin(sn);
+    } catch (RetriesExhaustedException e) {
+      if (e.getCause() != null && e.getCause() instanceof ConnectException) {
+        // Catch this; presume it means the cached connection has gone bad.
+      } else {
+        throw e;
+      }
+    } catch (SocketTimeoutException e) {
+      LOG.debug("Timed out connecting to " + sn);
+    } catch (NoRouteToHostException e) {
+      LOG.debug("Connecting to " + sn, e);
+    } catch (SocketException e) {
+      LOG.debug("Exception connecting to " + sn);
+    } catch (UnknownHostException e) {
+      LOG.debug("Unknown host exception connecting to  " + sn);
+    } catch (IOException ioe) {
+      Throwable cause = ioe.getCause();
+      if (ioe instanceof ConnectException) {
+        // Catch. Connect refused.
+      } else if (cause != null && cause instanceof EOFException) {
+        // Catch. Other end disconnected us.
+      } else if (cause != null && cause.getMessage() != null &&
+        cause.getMessage().toLowerCase().contains("connection reset")) {
+        // Catch. Connection reset.
+      } else {
+        throw ioe;
+      }
+      
+    }
+    return protocol;
+  }
+
+  /**
+   * Verify we can connect to <code>hostingServer</code> and that its carrying
+   * <code>regionName</code>.
+   * @param hostingServer Interface to the server hosting <code>regionName</code>
+   * @param serverName The servername that goes with the <code>metaServer</code>
+   * Interface.  Used logging.
+   * @param regionName The regionname we are interested in.
+   * @return True if we were able to verify the region located at other side of
+   * the Interface.
+   * @throws IOException
+   */
+  // TODO: We should be able to get the ServerName from the AdminProtocol
+  // rather than have to pass it in.  Its made awkward by the fact that the
+  // HRI is likely a proxy against remote server so the getServerName needs
+  // to be fixed to go to a local method or to a cache before we can do this.
+  private boolean verifyRegionLocation(AdminProtocol hostingServer,
+      final ServerName address, final byte [] regionName)
+  throws IOException {
+    if (hostingServer == null) {
+      LOG.info("Passed hostingServer is null");
+      return false;
+    }
+    Throwable t = null;
+    try {
+      // Try and get regioninfo from the hosting server.
+      return ProtobufUtil.getRegionInfo(hostingServer, regionName) != null;
+    } catch (ConnectException e) {
+      t = e;
+    } catch (RetriesExhaustedException e) {
+      t = e;
+    } catch (RemoteException e) {
+      IOException ioe = e.unwrapRemoteException();
+      t = ioe;
+    } catch (IOException e) {
+      Throwable cause = e.getCause();
+      if (cause != null && cause instanceof EOFException) {
+        t = cause;
+      } else if (cause != null && cause.getMessage() != null
+          && cause.getMessage().contains("Connection reset")) {
+        t = cause;
+      } else {
+        t = e;
+      }
+    }
+    LOG.info("Failed verification of " + Bytes.toStringBinary(regionName) +
+      " at address=" + address + "; " + t);
+    return false;
+  }
+
+  /**
+   * Verify <code>-ROOT-</code> is deployed and accessible.
+   * @param timeout How long to wait on zk for root address (passed through to
+   * the internal call to {@link #waitForRootServerConnection(long)}.
+   * @return True if the <code>-ROOT-</code> location is healthy.
+   * @throws IOException
+   * @throws InterruptedException 
+   */
+  public boolean verifyRootRegionLocation(final long timeout)
+  throws InterruptedException, IOException {
+    AdminProtocol connection = null;
+    try {
+      connection = waitForRootServerConnection(timeout);
+    } catch (NotAllMetaRegionsOnlineException e) {
+      // Pass
+    } catch (ServerNotRunningYetException e) {
+      // Pass -- remote server is not up so can't be carrying root
+    } catch (UnknownHostException e) {
+      // Pass -- server name doesn't resolve so it can't be assigned anything.
+    }
+    return (connection == null)? false:
+      verifyRegionLocation(connection,
+        this.rootRegionTracker.getRootRegionLocation(), ROOT_REGION_NAME);
+  }
+
+  /**
+   * Verify <code>.META.</code> is deployed and accessible.
+   * @param timeout How long to wait on zk for <code>.META.</code> address
+   * (passed through to the internal call to {@link #waitForMetaServerConnection(long)}.
+   * @return True if the <code>.META.</code> location is healthy.
+   * @throws IOException Some unexpected IOE.
+   * @throws InterruptedException
+   */
+  public boolean verifyMetaRegionLocation(final long timeout)
+  throws InterruptedException, IOException {
+    AdminProtocol connection = null;
+    try {
+      connection = waitForMetaServerConnection(timeout);
+    } catch (NotAllMetaRegionsOnlineException e) {
+      // Pass
+    } catch (ServerNotRunningYetException e) {
+      // Pass -- remote server is not up so can't be carrying .META.
+    } catch (UnknownHostException e) {
+      // Pass -- server name doesn't resolve so it can't be assigned anything.
+    } catch (RetriesExhaustedException e) {
+      // Pass -- failed after bunch of retries.
+      LOG.debug("Failed verify meta region location after retries", e);
+    }
+    return connection != null;
+  }
+
+  // Used by tests.
+  MetaNodeTracker getMetaNodeTracker() {
+    return this.metaNodeTracker;
+  }
+
+  public HConnection getConnection() {
+    return this.connection;
+  }
+}

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/MetaReader.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/MetaReader.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/MetaReader.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/catalog/MetaReader.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,642 @@
+/**
+ * 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.hadoop.hbase.catalog;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.HTable;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
+import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Reads region and assignment information from <code>.META.</code>.
+ */
+@InterfaceAudience.Private
+public class MetaReader {
+  // TODO: Strip CatalogTracker from this class.  Its all over and in the end
+  // its only used to get its Configuration so we can get associated
+  // Connection.
+  private static final Log LOG = LogFactory.getLog(MetaReader.class);
+
+  static final byte [] META_REGION_PREFIX;
+  static {
+    // Copy the prefix from FIRST_META_REGIONINFO into META_REGION_PREFIX.
+    // FIRST_META_REGIONINFO == '.META.,,1'.  META_REGION_PREFIX == '.META.,'
+    int len = HRegionInfo.FIRST_META_REGIONINFO.getRegionName().length - 2;
+    META_REGION_PREFIX = new byte [len];
+    System.arraycopy(HRegionInfo.FIRST_META_REGIONINFO.getRegionName(), 0,
+      META_REGION_PREFIX, 0, len);
+  }
+
+  /**
+   * @param row
+   * @return True if <code>row</code> is row of <code>-ROOT-</code> table.
+   */
+  private static boolean isRootTableRow(final byte [] row) {
+    if (row.length < META_REGION_PREFIX.length + 2 /* ',', + '1' */) {
+      // Can't be meta table region.
+      return false;
+    }
+    // Compare the prefix of row.  If it matches META_REGION_PREFIX prefix,
+    // then this is row from -ROOT_ table.
+    return Bytes.equals(row, 0, META_REGION_PREFIX.length,
+      META_REGION_PREFIX, 0, META_REGION_PREFIX.length);
+  }
+
+  /**
+   * Performs a full scan of <code>.META.</code>, skipping regions from any
+   * tables in the specified set of disabled tables.
+   * @param catalogTracker
+   * @param disabledTables set of disabled tables that will not be returned
+   * @return Returns a map of every region to it's currently assigned server,
+   * according to META.  If the region does not have an assignment it will have
+   * a null value in the map.
+   * @throws IOException
+   */
+  public static Map<HRegionInfo, ServerName> fullScan(
+      CatalogTracker catalogTracker, final Set<String> disabledTables)
+  throws IOException {
+    return fullScan(catalogTracker, disabledTables, false);
+  }
+
+  /**
+   * Performs a full scan of <code>.META.</code>, skipping regions from any
+   * tables in the specified set of disabled tables.
+   * @param catalogTracker
+   * @param disabledTables set of disabled tables that will not be returned
+   * @param excludeOfflinedSplitParents If true, do not include offlined split
+   * parents in the return.
+   * @return Returns a map of every region to it's currently assigned server,
+   * according to META.  If the region does not have an assignment it will have
+   * a null value in the map.
+   * @throws IOException
+   */
+  public static Map<HRegionInfo, ServerName> fullScan(
+      CatalogTracker catalogTracker, final Set<String> disabledTables,
+      final boolean excludeOfflinedSplitParents)
+  throws IOException {
+    final Map<HRegionInfo, ServerName> regions =
+      new TreeMap<HRegionInfo, ServerName>();
+    Visitor v = new Visitor() {
+      @Override
+      public boolean visit(Result r) throws IOException {
+        if (r ==  null || r.isEmpty()) return true;
+        Pair<HRegionInfo, ServerName> region = HRegionInfo.getHRegionInfoAndServerName(r);
+        HRegionInfo hri = region.getFirst();
+        if (hri  == null) return true;
+        if (hri.getTableNameAsString() == null) return true;
+        if (disabledTables.contains(
+            hri.getTableNameAsString())) return true;
+        // Are we to include split parents in the list?
+        if (excludeOfflinedSplitParents && hri.isSplitParent()) return true;
+        regions.put(hri, region.getSecond());
+        return true;
+      }
+    };
+    fullScan(catalogTracker, v);
+    return regions;
+  }
+
+  /**
+   * Performs a full scan of <code>.META.</code>.
+   * @return List of {@link Result}
+   * @throws IOException
+   */
+  public static List<Result> fullScan(CatalogTracker catalogTracker)
+  throws IOException {
+    CollectAllVisitor v = new CollectAllVisitor();
+    fullScan(catalogTracker, v, null);
+    return v.getResults();
+  }
+
+  /**
+   * Performs a full scan of a <code>-ROOT-</code> table.
+   * @return List of {@link Result}
+   * @throws IOException
+   */
+  public static List<Result> fullScanOfRoot(CatalogTracker catalogTracker)
+  throws IOException {
+    CollectAllVisitor v = new CollectAllVisitor();
+    fullScan(catalogTracker, v, null, true);
+    return v.getResults();
+  }
+
+  /**
+   * Performs a full scan of <code>.META.</code>.
+   * @param catalogTracker
+   * @param visitor Visitor invoked against each row.
+   * @throws IOException
+   */
+  public static void fullScan(CatalogTracker catalogTracker,
+      final Visitor visitor)
+  throws IOException {
+    fullScan(catalogTracker, visitor, null);
+  }
+
+  /**
+   * Performs a full scan of <code>.META.</code>.
+   * @param catalogTracker
+   * @param visitor Visitor invoked against each row.
+   * @param startrow Where to start the scan. Pass null if want to begin scan
+   * at first row (The visitor will stop the Scan when its done so no need to
+   * pass a stoprow).
+   * @throws IOException
+   */
+  public static void fullScan(CatalogTracker catalogTracker,
+      final Visitor visitor, final byte [] startrow)
+  throws IOException {
+    fullScan(catalogTracker, visitor, startrow, false);
+  }
+
+  /**
+   * Callers should call close on the returned {@link HTable} instance.
+   * @param catalogTracker We'll use this catalogtracker's connection
+   * @param tableName Table to get an {@link HTable} against.
+   * @return An {@link HTable} for <code>tableName</code>
+   * @throws IOException
+   */
+  private static HTable getHTable(final CatalogTracker catalogTracker,
+      final byte [] tableName)
+  throws IOException {
+    // Passing the CatalogTracker's connection configuration ensures this
+    // HTable instance uses the CatalogTracker's connection.
+    org.apache.hadoop.hbase.client.HConnection c = catalogTracker.getConnection();
+    if (c == null) throw new NullPointerException("No connection");
+    return new HTable(catalogTracker.getConnection().getConfiguration(), tableName);
+  }
+
+  /**
+   * Callers should call close on the returned {@link HTable} instance.
+   * @param catalogTracker
+   * @param row Row we are putting
+   * @return
+   * @throws IOException
+   */
+  static HTable getCatalogHTable(final CatalogTracker catalogTracker,
+      final byte [] row)
+  throws IOException {
+    return isRootTableRow(row)?
+      getRootHTable(catalogTracker):
+      getMetaHTable(catalogTracker);
+  }
+
+  /**
+   * Callers should call close on the returned {@link HTable} instance.
+   * @param ct
+   * @return An {@link HTable} for <code>.META.</code>
+   * @throws IOException
+   */
+  static HTable getMetaHTable(final CatalogTracker ct)
+  throws IOException {
+    return getHTable(ct, HConstants.META_TABLE_NAME);
+  }
+
+  /**
+   * Callers should call close on the returned {@link HTable} instance.
+   * @param ct
+   * @return An {@link HTable} for <code>-ROOT-</code>
+   * @throws IOException
+   */
+  static HTable getRootHTable(final CatalogTracker ct)
+  throws IOException {
+    return getHTable(ct, HConstants.ROOT_TABLE_NAME);
+  }
+
+  /**
+   * @param t Table to use (will be closed when done).
+   * @param g Get to run
+   * @throws IOException
+   */
+  private static Result get(final HTable t, final Get g) throws IOException {
+    try {
+      return t.get(g);
+    } finally {
+      t.close();
+    }
+  }
+
+  /**
+   * Gets the location of <code>.META.</code> region by reading content of
+   * <code>-ROOT-</code>.
+   * @param ct
+   * @return location of <code>.META.</code> region as a {@link ServerName} or
+   * null if not found
+   * @throws IOException
+   */
+  static ServerName getMetaRegionLocation(final CatalogTracker ct)
+  throws IOException {
+    return MetaReader.readRegionLocation(ct, CatalogTracker.META_REGION_NAME);
+  }
+
+  /**
+   * Reads the location of the specified region
+   * @param catalogTracker
+   * @param regionName region whose location we are after
+   * @return location of region as a {@link ServerName} or null if not found
+   * @throws IOException
+   */
+  static ServerName readRegionLocation(CatalogTracker catalogTracker,
+      byte [] regionName)
+  throws IOException {
+    Pair<HRegionInfo, ServerName> pair = getRegion(catalogTracker, regionName);
+    return (pair == null || pair.getSecond() == null)? null: pair.getSecond();
+  }
+
+  /**
+   * Gets the region info and assignment for the specified region.
+   * @param catalogTracker
+   * @param regionName Region to lookup.
+   * @return Location and HRegionInfo for <code>regionName</code>
+   * @throws IOException
+   */
+  public static Pair<HRegionInfo, ServerName> getRegion(
+      CatalogTracker catalogTracker, byte [] regionName)
+  throws IOException {
+    Get get = new Get(regionName);
+    get.addFamily(HConstants.CATALOG_FAMILY);
+    Result r = get(getCatalogHTable(catalogTracker, regionName), get);
+    return (r == null || r.isEmpty())? null: HRegionInfo.getHRegionInfoAndServerName(r);
+  }
+
+  /**
+   * Checks if the specified table exists.  Looks at the META table hosted on
+   * the specified server.
+   * @param catalogTracker
+   * @param tableName table to check
+   * @return true if the table exists in meta, false if not
+   * @throws IOException
+   */
+  public static boolean tableExists(CatalogTracker catalogTracker,
+      String tableName)
+  throws IOException {
+    if (tableName.equals(HTableDescriptor.ROOT_TABLEDESC.getNameAsString()) ||
+        tableName.equals(HTableDescriptor.META_TABLEDESC.getNameAsString())) {
+      // Catalog tables always exist.
+      return true;
+    }
+    final byte [] tableNameBytes = Bytes.toBytes(tableName);
+    // Make a version of ResultCollectingVisitor that only collects the first
+    CollectingVisitor<HRegionInfo> visitor = new CollectingVisitor<HRegionInfo>() {
+      private HRegionInfo current = null;
+
+      @Override
+      public boolean visit(Result r) throws IOException {
+        this.current =
+          HRegionInfo.getHRegionInfo(r, HConstants.REGIONINFO_QUALIFIER);
+        if (this.current == null) {
+          LOG.warn("No serialized HRegionInfo in " + r);
+          return true;
+        }
+        if (!isInsideTable(this.current, tableNameBytes)) return false;
+        // Else call super and add this Result to the collection.
+        super.visit(r);
+        // Stop collecting regions from table after we get one.
+        return false;
+      }
+
+      @Override
+      void add(Result r) {
+        // Add the current HRI.
+        this.results.add(this.current);
+      }
+    };
+    fullScan(catalogTracker, visitor, getTableStartRowForMeta(tableNameBytes));
+    // If visitor has results >= 1 then table exists.
+    return visitor.getResults().size() >= 1;
+  }
+
+  /**
+   * Gets all of the regions of the specified table.
+   * @param catalogTracker
+   * @param tableName
+   * @return Ordered list of {@link HRegionInfo}.
+   * @throws IOException
+   */
+  public static List<HRegionInfo> getTableRegions(CatalogTracker catalogTracker,
+      byte [] tableName)
+  throws IOException {
+    return getTableRegions(catalogTracker, tableName, false);
+  }
+
+  /**
+   * Gets all of the regions of the specified table.
+   * @param catalogTracker
+   * @param tableName
+   * @param excludeOfflinedSplitParents If true, do not include offlined split
+   * parents in the return.
+   * @return Ordered list of {@link HRegionInfo}.
+   * @throws IOException
+   */
+  public static List<HRegionInfo> getTableRegions(CatalogTracker catalogTracker,
+      byte [] tableName, final boolean excludeOfflinedSplitParents)
+  throws IOException {
+    List<Pair<HRegionInfo, ServerName>> result = null;
+    try {
+      result = getTableRegionsAndLocations(catalogTracker, tableName,
+        excludeOfflinedSplitParents);
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+    return getListOfHRegionInfos(result);
+  }
+
+  static List<HRegionInfo> getListOfHRegionInfos(final List<Pair<HRegionInfo, ServerName>> pairs) {
+    if (pairs == null || pairs.isEmpty()) return null;
+    List<HRegionInfo> result = new ArrayList<HRegionInfo>(pairs.size());
+    for (Pair<HRegionInfo, ServerName> pair: pairs) {
+      result.add(pair.getFirst());
+    }
+    return result;
+  }
+
+  /**
+   * @param current
+   * @param tableName
+   * @return True if <code>current</code> tablename is equal to
+   * <code>tableName</code>
+   */
+  static boolean isInsideTable(final HRegionInfo current, final byte [] tableName) {
+    return Bytes.equals(tableName, current.getTableName());
+  }
+
+  /**
+   * @param tableName
+   * @return Place to start Scan in <code>.META.</code> when passed a
+   * <code>tableName</code>; returns &lt;tableName&rt; &lt;,&rt; &lt;,&rt;
+   */
+  static byte [] getTableStartRowForMeta(final byte [] tableName) {
+    byte [] startRow = new byte[tableName.length + 2];
+    System.arraycopy(tableName, 0, startRow, 0, tableName.length);
+    startRow[startRow.length - 2] = HConstants.DELIMITER;
+    startRow[startRow.length - 1] = HConstants.DELIMITER;
+    return startRow;
+  }
+
+  /**
+   * This method creates a Scan object that will only scan catalog rows that
+   * belong to the specified table. It doesn't specify any columns.
+   * This is a better alternative to just using a start row and scan until
+   * it hits a new table since that requires parsing the HRI to get the table
+   * name.
+   * @param tableName bytes of table's name
+   * @return configured Scan object
+   */
+  public static Scan getScanForTableName(byte[] tableName) {
+    String strName = Bytes.toString(tableName);
+    // Start key is just the table name with delimiters
+    byte[] startKey = Bytes.toBytes(strName + ",,");
+    // Stop key appends the smallest possible char to the table name
+    byte[] stopKey = Bytes.toBytes(strName + " ,,");
+
+    Scan scan = new Scan(startKey);
+    scan.setStopRow(stopKey);
+    return scan;
+  }
+
+  /**
+   * @param catalogTracker
+   * @param tableName
+   * @return Return list of regioninfos and server.
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  public static List<Pair<HRegionInfo, ServerName>>
+  getTableRegionsAndLocations(CatalogTracker catalogTracker, String tableName)
+  throws IOException, InterruptedException {
+    return getTableRegionsAndLocations(catalogTracker, Bytes.toBytes(tableName),
+      true);
+  }
+
+  /**
+   * @param catalogTracker
+   * @param tableName
+   * @return Return list of regioninfos and server addresses.
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  public static List<Pair<HRegionInfo, ServerName>>
+  getTableRegionsAndLocations(final CatalogTracker catalogTracker,
+      final byte [] tableName, final boolean excludeOfflinedSplitParents)
+  throws IOException, InterruptedException {
+    if (Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME)) {
+      // If root, do a bit of special handling.
+      ServerName serverName = catalogTracker.getRootLocation();
+      List<Pair<HRegionInfo, ServerName>> list =
+        new ArrayList<Pair<HRegionInfo, ServerName>>();
+      list.add(new Pair<HRegionInfo, ServerName>(HRegionInfo.ROOT_REGIONINFO,
+        serverName));
+      return list;
+    }
+    // Make a version of CollectingVisitor that collects HRegionInfo and ServerAddress
+    CollectingVisitor<Pair<HRegionInfo, ServerName>> visitor =
+        new CollectingVisitor<Pair<HRegionInfo, ServerName>>() {
+      private Pair<HRegionInfo, ServerName> current = null;
+
+      @Override
+      public boolean visit(Result r) throws IOException {
+        HRegionInfo hri =
+          HRegionInfo.getHRegionInfo(r, HConstants.REGIONINFO_QUALIFIER);
+        if (hri == null) {
+          LOG.warn("No serialized HRegionInfo in " + r);
+          return true;
+        }
+        if (!isInsideTable(hri, tableName)) return false;
+        if (excludeOfflinedSplitParents && hri.isSplitParent()) return true;
+        ServerName sn = HRegionInfo.getServerName(r);
+        // Populate this.current so available when we call #add
+        this.current = new Pair<HRegionInfo, ServerName>(hri, sn);
+        // Else call super and add this Result to the collection.
+        return super.visit(r);
+      }
+
+      @Override
+      void add(Result r) {
+        this.results.add(this.current);
+      }
+    };
+    fullScan(catalogTracker, visitor, getTableStartRowForMeta(tableName),
+      Bytes.equals(tableName, HConstants.META_TABLE_NAME));
+    return visitor.getResults();
+  }
+
+  /**
+   * @param catalogTracker
+   * @param serverName
+   * @return List of user regions installed on this server (does not include
+   * catalog regions).
+   * @throws IOException
+   */
+  public static NavigableMap<HRegionInfo, Result>
+  getServerUserRegions(CatalogTracker catalogTracker, final ServerName serverName)
+  throws IOException {
+    final NavigableMap<HRegionInfo, Result> hris = new TreeMap<HRegionInfo, Result>();
+    // Fill the above hris map with entries from .META. that have the passed
+    // servername.
+    CollectingVisitor<Result> v = new CollectingVisitor<Result>() {
+      @Override
+      void add(Result r) {
+        if (r == null || r.isEmpty()) return;
+        ServerName sn = HRegionInfo.getServerName(r);
+        if (sn != null && sn.equals(serverName)) this.results.add(r);
+      }
+    };
+    fullScan(catalogTracker, v);
+    List<Result> results = v.getResults();
+    if (results != null && !results.isEmpty()) {
+      // Convert results to Map keyed by HRI
+      for (Result r: results) {
+        Pair<HRegionInfo, ServerName> p = HRegionInfo.getHRegionInfoAndServerName(r);
+        if (p != null && p.getFirst() != null) hris.put(p.getFirst(), r);
+      }
+    }
+    return hris;
+  }
+
+  public static void fullScanMetaAndPrint(final CatalogTracker catalogTracker)
+  throws IOException {
+    Visitor v = new Visitor() {
+      @Override
+      public boolean visit(Result r) throws IOException {
+        if (r ==  null || r.isEmpty()) return true;
+        LOG.info("fullScanMetaAndPrint.Current Meta Row: " + r);
+        HRegionInfo hrim = HRegionInfo.getHRegionInfo(r);
+        LOG.info("fullScanMetaAndPrint.HRI Print= " + hrim);
+        return true;
+      }
+    };
+    fullScan(catalogTracker, v);
+  }
+
+  /**
+   * Performs a full scan of a catalog table.
+   * @param catalogTracker
+   * @param visitor Visitor invoked against each row.
+   * @param startrow Where to start the scan. Pass null if want to begin scan
+   * at first row.
+   * @param scanRoot True if we are to scan <code>-ROOT-</code> rather than
+   * <code>.META.</code>, the default (pass false to scan .META.)
+   * @throws IOException
+   */
+  static void fullScan(CatalogTracker catalogTracker,
+    final Visitor visitor, final byte [] startrow, final boolean scanRoot)
+  throws IOException {
+    Scan scan = new Scan();
+    if (startrow != null) scan.setStartRow(startrow);
+    if (startrow == null && !scanRoot) {
+      int caching = catalogTracker.getConnection().getConfiguration()
+          .getInt(HConstants.HBASE_META_SCANNER_CACHING, 100);
+      scan.setCaching(caching);
+    }
+    scan.addFamily(HConstants.CATALOG_FAMILY);
+    HTable metaTable = scanRoot?
+      getRootHTable(catalogTracker): getMetaHTable(catalogTracker);
+    ResultScanner scanner = metaTable.getScanner(scan);
+    try {
+      Result data;
+      while((data = scanner.next()) != null) {
+        if (data.isEmpty()) continue;
+        // Break if visit returns false.
+        if (!visitor.visit(data)) break;
+      }
+    } finally {
+      scanner.close();
+      metaTable.close();
+    }
+    return;
+  }
+
+  /**
+   * Implementations 'visit' a catalog table row.
+   */
+  public interface Visitor {
+    /**
+     * Visit the catalog table row.
+     * @param r A row from catalog table
+     * @return True if we are to proceed scanning the table, else false if
+     * we are to stop now.
+     */
+    public boolean visit(final Result r) throws IOException;
+  }
+
+  /**
+   * A {@link Visitor} that collects content out of passed {@link Result}.
+   */
+  static abstract class CollectingVisitor<T> implements Visitor {
+    final List<T> results = new ArrayList<T>();
+    @Override
+    public boolean visit(Result r) throws IOException {
+      if (r ==  null || r.isEmpty()) return true;
+      add(r);
+      return true;
+    }
+
+    abstract void add(Result r);
+
+    /**
+     * @return Collected results; wait till visits complete to collect all
+     * possible results
+     */
+    List<T> getResults() {
+      return this.results;
+    }
+  }
+
+  /**
+   * Collects all returned.
+   */
+  static class CollectAllVisitor extends CollectingVisitor<Result> {
+    @Override
+    void add(Result r) {
+      this.results.add(r);
+    }
+  }
+
+  /**
+   * Count regions in <code>.META.</code> for passed table.
+   * @param c
+   * @param tableName
+   * @return Count or regions in table <code>tableName</code>
+   * @throws IOException
+   */
+  public static int getRegionCount(final Configuration c, final String tableName) throws IOException {
+    HTable t = new HTable(c, tableName);
+    try {
+      return t.getRegionLocations().size();
+    } finally {
+      t.close();
+    }
+  }
+}

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,77 @@
+/**
+ * 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.hadoop.hbase.client;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+/**
+ * Helper class for custom client scanners.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Stable
+public abstract class AbstractClientScanner implements ResultScanner {
+
+  @Override
+  public Iterator<Result> iterator() {
+    return new Iterator<Result>() {
+      // The next RowResult, possibly pre-read
+      Result next = null;
+
+      // return true if there is another item pending, false if there isn't.
+      // this method is where the actual advancing takes place, but you need
+      // to call next() to consume it. hasNext() will only advance if there
+      // isn't a pending next().
+      public boolean hasNext() {
+        if (next == null) {
+          try {
+            next = AbstractClientScanner.this.next();
+            return next != null;
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        }
+        return true;
+      }
+
+      // get the pending next item and advance the iterator. returns null if
+      // there is no next item.
+      public Result next() {
+        // since hasNext() does the real advancing, we call this to determine
+        // if there is a next before proceeding.
+        if (!hasNext()) {
+          return null;
+        }
+
+        // if we get to here, then hasNext() has given us an item to return.
+        // we want to return the item and then null out the next pointer, so
+        // we use a temporary variable.
+        Result temp = next;
+        next = null;
+        return temp;
+      }
+
+      public void remove() {
+        throw new UnsupportedOperationException();
+      }
+    };
+  }
+}

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Action.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Action.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Action.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Action.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,88 @@
+/**
+ *
+ * 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.hadoop.hbase.client;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A Get, Put or Delete associated with it's region.  Used internally by  
+ * {@link HTable#batch} to associate the action with it's region and maintain
+ * the index from the original request. 
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Stable
+public class Action<R> implements Comparable<R> {
+
+  private Row action;
+  private int originalIndex;
+  private R result;
+
+  /**
+   * This constructor is replaced by {@link #Action(Row, int)}
+   */
+  @Deprecated
+  public Action(byte[] regionName, Row action, int originalIndex) {
+    this(action, originalIndex);
+  }
+
+  public Action(Row action, int originalIndex) {
+    super();
+    this.action = action;
+    this.originalIndex = originalIndex;    
+  }
+  
+  @Deprecated
+  public byte[] getRegionName() {
+    return null;
+  }
+
+  @Deprecated
+  public void setRegionName(byte[] regionName) {
+  }
+
+  public R getResult() {
+    return result;
+  }
+
+  public void setResult(R result) {
+    this.result = result;
+  }
+
+  public Row getAction() {
+    return action;
+  }
+
+  public int getOriginalIndex() {
+    return originalIndex;
+  }
+
+  @Override
+  public int compareTo(Object o) {
+    return action.compareTo(((Action) o).getAction());
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null || getClass() != obj.getClass()) return false;
+    Action<?> other = (Action<?>) obj;
+    return compareTo(other) == 0;
+  }
+}

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminProtocol.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminProtocol.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminProtocol.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminProtocol.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,35 @@
+/**
+ * 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.hadoop.hbase.client;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.hbase.IpcProtocol;
+import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.AdminService;
+import org.apache.hadoop.hbase.security.KerberosInfo;
+import org.apache.hadoop.hbase.security.TokenInfo;
+
+/**
+ * Protocol that a HBase client uses to communicate with a region server.
+ */
+@KerberosInfo(
+  serverPrincipal = "hbase.regionserver.kerberos.principal")
+@TokenInfo("HBASE_AUTH_TOKEN")
+@InterfaceAudience.Private
+public interface AdminProtocol
+extends AdminService.BlockingInterface, IpcProtocol {}
\ No newline at end of file

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Append.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Append.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Append.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Append.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,90 @@
+/*
+ * 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.hadoop.hbase.client;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.util.Bytes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Performs Append operations on a single row.
+ * <p>
+ * Note that this operation does not appear atomic to readers. Appends are done
+ * under a single row lock, so write operations to a row are synchronized, but
+ * readers do not take row locks so get and scan operations can see this
+ * operation partially completed.
+ * <p>
+ * To append to a set of columns of a row, instantiate an Append object with the
+ * row to append to. At least one column to append must be specified using the
+ * {@link #add(byte[], byte[], byte[])} method.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Stable
+public class Append extends Mutation {
+  private static final String RETURN_RESULTS = "_rr_";
+  /**
+   * @param returnResults
+   *          True (default) if the append operation should return the results.
+   *          A client that is not interested in the result can save network
+   *          bandwidth setting this to false.
+   */
+  public void setReturnResults(boolean returnResults) {
+    setAttribute(RETURN_RESULTS, Bytes.toBytes(returnResults));
+  }
+
+  /**
+   * @return current setting for returnResults
+   */
+  public boolean isReturnResults() {
+    byte[] v = getAttribute(RETURN_RESULTS);
+    return v == null ? true : Bytes.toBoolean(v);
+  }
+
+  /**
+   * Create a Append operation for the specified row.
+   * <p>
+   * At least one column must be appended to.
+   * @param row row key
+   */
+  public Append(byte[] row) {
+    this.row = Arrays.copyOf(row, row.length);
+  }
+
+  /**
+   * Add the specified column and value to this Append operation.
+   * @param family family name
+   * @param qualifier column qualifier
+   * @param value value to append to specified column
+   * @return this
+   */
+  public Append add(byte [] family, byte [] qualifier, byte [] value) {
+    List<KeyValue> list = familyMap.get(family);
+    if(list == null) {
+      list = new ArrayList<KeyValue>();
+    }
+    list.add(new KeyValue(
+        this.row, family, qualifier, this.ts, KeyValue.Type.Put, value));
+    familyMap.put(family, list);
+    return this;
+  }
+}
\ No newline at end of file

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Attributes.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Attributes.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Attributes.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Attributes.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,51 @@
+/*
+ *
+ * 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.hadoop.hbase.client;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.util.Map;
+
+@InterfaceAudience.Public
+@InterfaceStability.Stable
+public interface Attributes {
+  /**
+   * Sets an attribute.
+   * In case value = null attribute is removed from the attributes map.
+   * Attribute names starting with _ indicate system attributes.
+   * @param name attribute name
+   * @param value attribute value
+   */
+  public void setAttribute(String name, byte[] value);
+
+  /**
+   * Gets an attribute
+   * @param name attribute name
+   * @return attribute value if attribute is set, <tt>null</tt> otherwise
+   */
+  public byte[] getAttribute(String name);
+
+  /**
+   * Gets all attributes
+   * @return unmodifiable map of all attributes
+   */
+  public Map<String, byte[]> getAttributesMap();
+}

Added: hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientProtocol.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientProtocol.java?rev=1449950&view=auto
==============================================================================
--- hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientProtocol.java (added)
+++ hbase/trunk/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientProtocol.java Mon Feb 25 22:50:17 2013
@@ -0,0 +1,37 @@
+/**
+ * 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.hadoop.hbase.client;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.hbase.IpcProtocol;
+import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ClientService;
+import org.apache.hadoop.hbase.security.KerberosInfo;
+import org.apache.hadoop.hbase.security.TokenInfo;
+
+/**
+ * Protocol that a HBase client uses to communicate with a region server.
+ */
+@KerberosInfo(
+  serverPrincipal = "hbase.regionserver.kerberos.principal")
+@TokenInfo("HBASE_AUTH_TOKEN")
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public interface ClientProtocol
+extends ClientService.BlockingInterface, IpcProtocol {}
\ No newline at end of file