You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by en...@apache.org on 2014/06/28 02:30:51 UTC

[05/49] HBASE-10347 HRegionInfo changes for adding replicaId and MetaEditor/MetaReader changes for region replicas

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java
index ce055ca..15f78c5 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java
@@ -34,8 +34,6 @@ import java.util.NavigableMap;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-import java.util.concurrent.ConcurrentSkipListSet;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -54,6 +52,7 @@ import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.MasterNotRunningException;
+import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.RegionTooBusyException;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.Stoppable;
@@ -61,6 +60,7 @@ import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.TableNotEnabledException;
 import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.hadoop.hbase.catalog.MetaReader;
 import org.apache.hadoop.hbase.client.AsyncProcess.AsyncRequestFuture;
 import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
 import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitorBase;
@@ -350,6 +350,7 @@ class ConnectionManager {
    * @param conf configuration whose identity is used to find {@link HConnection} instance.
    * @deprecated
    */
+  @Deprecated
   public static void deleteConnection(Configuration conf) {
     deleteConnection(new HConnectionKey(conf), false);
   }
@@ -361,6 +362,7 @@ class ConnectionManager {
    * @param connection
    * @deprecated
    */
+  @Deprecated
   public static void deleteStaleConnection(HConnection connection) {
     deleteConnection(connection, true);
   }
@@ -371,6 +373,7 @@ class ConnectionManager {
    *  staleConnection to true.
    * @deprecated
    */
+  @Deprecated
   public static void deleteAllConnections(boolean staleConnection) {
     synchronized (CONNECTION_INSTANCES) {
       Set<HConnectionKey> connectionKeys = new HashSet<HConnectionKey>();
@@ -496,19 +499,7 @@ class ConnectionManager {
     // Client rpc instance.
     private RpcClient rpcClient;
 
-    /**
-      * Map of table to table {@link HRegionLocation}s.
-      */
-    private final ConcurrentMap<TableName, ConcurrentSkipListMap<byte[], HRegionLocation>>
-        cachedRegionLocations =
-      new ConcurrentHashMap<TableName, ConcurrentSkipListMap<byte[], HRegionLocation>>();
-
-    // The presence of a server in the map implies it's likely that there is an
-    // entry in cachedRegionLocations that map to this server; but the absence
-    // of a server in this map guarentees that there is no entry in cache that
-    // maps to the absent server.
-    // The access to this attribute must be protected by a lock on cachedRegionLocations
-    private final Set<ServerName> cachedServers = new ConcurrentSkipListSet<ServerName>();
+    private MetaCache metaCache = new MetaCache();
 
     private int refCount;
 
@@ -731,6 +722,7 @@ class ConnectionManager {
      * An identifier that will remain the same for a given connection.
      * @return
      */
+    @Override
     public String toString(){
       return "hconnection-0x" + Integer.toHexString(hashCode());
     }
@@ -902,8 +894,9 @@ class ConnectionManager {
 
     @Override
     public HRegionLocation locateRegion(final byte[] regionName) throws IOException {
-      return locateRegion(HRegionInfo.getTable(regionName),
-          HRegionInfo.getStartKey(regionName), false, true);
+      RegionLocations locations = locateRegion(HRegionInfo.getTable(regionName),
+        HRegionInfo.getStartKey(regionName), false, true);
+      return locations == null ? null : locations.getRegionLocation();
     }
 
     @Override
@@ -934,7 +927,14 @@ class ConnectionManager {
           tableName, offlined);
       final List<HRegionLocation> locations = new ArrayList<HRegionLocation>();
       for (HRegionInfo regionInfo : regions.keySet()) {
-        locations.add(locateRegion(tableName, regionInfo.getStartKey(), useCache, true));
+        RegionLocations list = locateRegion(tableName, regionInfo.getStartKey(), useCache, true);
+        if (list != null) {
+          for (HRegionLocation loc : list.getRegionLocations()) {
+            if (loc != null) {
+              locations.add(loc);
+            }
+          }
+        }
       }
       return locations;
     }
@@ -949,7 +949,8 @@ class ConnectionManager {
     public HRegionLocation locateRegion(final TableName tableName,
         final byte [] row)
     throws IOException{
-      return locateRegion(tableName, row, true, true);
+      RegionLocations locations = locateRegion(tableName, row, true, true);
+      return locations == null ? null : locations.getRegionLocation();
     }
 
     @Override
@@ -969,7 +970,8 @@ class ConnectionManager {
         throw new TableNotEnabledException(tableName.getNameAsString() + " is disabled.");
       }
 
-      return locateRegion(tableName, row, false, true);
+      RegionLocations locations = locateRegion(tableName, row, false, true);
+      return locations == null ? null : locations.getRegionLocation();
     }
 
     @Override
@@ -979,7 +981,7 @@ class ConnectionManager {
     }
 
 
-    private HRegionLocation locateRegion(final TableName tableName,
+    private RegionLocations locateRegion(final TableName tableName,
       final byte [] row, boolean useCache, boolean retry)
     throws IOException {
       if (this.closed) throw new IOException(toString() + " closed");
@@ -1000,15 +1002,15 @@ class ConnectionManager {
       * Search the hbase:meta table for the HRegionLocation
       * info that contains the table and row we're seeking.
       */
-    private HRegionLocation locateRegionInMeta(TableName tableName, byte[] row,
+    private RegionLocations locateRegionInMeta(TableName tableName, byte[] row,
                    boolean useCache, boolean retry) throws IOException {
 
       // If we are supposed to be using the cache, look in the cache to see if
       // we already have the region.
       if (useCache) {
-        HRegionLocation location = getCachedLocation(tableName, row);
-        if (location != null) {
-          return location;
+        RegionLocations locations = getCachedLocation(tableName, row);
+        if (locations != null) {
+          return locations;
         }
       }
 
@@ -1033,9 +1035,9 @@ class ConnectionManager {
               " after " + localNumRetries + " tries.");
         }
         if (useCache) {
-          HRegionLocation location = getCachedLocation(tableName, row);
-          if (location != null) {
-            return location;
+          RegionLocations locations = getCachedLocation(tableName, row);
+          if (locations != null) {
+            return locations;
           }
         }
 
@@ -1057,7 +1059,8 @@ class ConnectionManager {
           }
 
           // convert the row result into the HRegionLocation we need!
-          HRegionInfo regionInfo = MetaScanner.getHRegionInfo(regionInfoRow);
+          RegionLocations locations = MetaReader.getRegionLocations(regionInfoRow);
+          HRegionInfo regionInfo = locations.getRegionLocation().getRegionInfo();
           if (regionInfo == null) {
             throw new IOException("HRegionInfo was null or empty in " +
               TableName.META_TABLE_NAME + ", row=" + regionInfoRow);
@@ -1081,7 +1084,7 @@ class ConnectionManager {
               regionInfo.getRegionNameAsString());
           }
 
-          ServerName serverName = HRegionInfo.getServerName(regionInfoRow);
+          ServerName serverName = locations.getRegionLocation().getServerName();
           if (serverName == null) {
             throw new NoServerForRegionException("No server address listed " +
               "in " + TableName.META_TABLE_NAME + " for region " +
@@ -1096,10 +1099,8 @@ class ConnectionManager {
           }
 
           // Instantiate the location
-          HRegionLocation location = new HRegionLocation(regionInfo, serverName,
-            HRegionInfo.getSeqNumDuringOpen(regionInfoRow));
-          cacheLocation(tableName, null, location);
-          return location;
+          cacheLocation(tableName, locations);
+          return locations;
 
         } catch (TableNotFoundException e) {
           // if we got this error, probably means the table just plain doesn't
@@ -1138,7 +1139,16 @@ class ConnectionManager {
       }
     }
 
-    /*
+    /**
+     * Put a newly discovered HRegionLocation into the cache.
+     * @param tableName The table name.
+     * @param location the new location
+     */
+    private void cacheLocation(final TableName tableName, final RegionLocations location) {
+      metaCache.cacheLocation(tableName, location);
+    }
+
+    /**
      * Search the cache for a location that fits our table and row key.
      * Return null if no suitable region is located.
      *
@@ -1146,52 +1156,13 @@ class ConnectionManager {
      * @param row
      * @return Null or region location found in cache.
      */
-    HRegionLocation getCachedLocation(final TableName tableName,
+    RegionLocations getCachedLocation(final TableName tableName,
         final byte [] row) {
-      ConcurrentSkipListMap<byte[], HRegionLocation> tableLocations =
-        getTableLocations(tableName);
-
-      Entry<byte[], HRegionLocation> e = tableLocations.floorEntry(row);
-      if (e == null) {
-        return null;
-      }
-      HRegionLocation possibleRegion = e.getValue();
-
-      // make sure that the end key is greater than the row we're looking
-      // for, otherwise the row actually belongs in the next region, not
-      // this one. the exception case is when the endkey is
-      // HConstants.EMPTY_END_ROW, signifying that the region we're
-      // checking is actually the last region in the table.
-      byte[] endKey = possibleRegion.getRegionInfo().getEndKey();
-      if (Bytes.equals(endKey, HConstants.EMPTY_END_ROW) ||
-          tableName.getRowComparator().compareRows(
-              endKey, 0, endKey.length, row, 0, row.length) > 0) {
-        return possibleRegion;
-      }
-
-      // Passed all the way through, so we got nothing - complete cache miss
-      return null;
+      return metaCache.getCachedLocation(tableName, row);
     }
 
-    /**
-     * Delete a cached location, no matter what it is. Called when we were told to not use cache.
-     * @param tableName tableName
-     * @param row
-     */
-    void forceDeleteCachedLocation(final TableName tableName, final byte [] row) {
-      HRegionLocation rl = null;
-      Map<byte[], HRegionLocation> tableLocations = getTableLocations(tableName);
-      // start to examine the cache. we can only do cache actions
-      // if there's something in the cache for this table.
-      rl = getCachedLocation(tableName, row);
-      if (rl != null) {
-        tableLocations.remove(rl.getRegionInfo().getStartKey());
-      }
-      if ((rl != null) && LOG.isDebugEnabled()) {
-        LOG.debug("Removed " + rl.getHostname() + ":" + rl.getPort()
-          + " as a location of " + rl.getRegionInfo().getRegionNameAsString() +
-          " for tableName=" + tableName + " from cache");
-      }
+    public void clearRegionCache(final TableName tableName, byte[] row) {
+      metaCache.clearCache(tableName, row);
     }
 
     /*
@@ -1199,66 +1170,17 @@ class ConnectionManager {
      */
     @Override
     public void clearCaches(final ServerName serverName) {
-      if (!this.cachedServers.contains(serverName)) {
-        return;
-      }
-
-      boolean deletedSomething = false;
-      synchronized (this.cachedServers) {
-        // We block here, because if there is an error on a server, it's likely that multiple
-        //  threads will get the error  simultaneously. If there are hundreds of thousand of
-        //  region location to check, it's better to do this only once. A better pattern would
-        //  be to check if the server is dead when we get the region location.
-        if (!this.cachedServers.contains(serverName)) {
-          return;
-        }
-        for (Map<byte[], HRegionLocation> tableLocations : cachedRegionLocations.values()) {
-          for (Entry<byte[], HRegionLocation> e : tableLocations.entrySet()) {
-            HRegionLocation value = e.getValue();
-            if (value != null
-                && serverName.equals(value.getServerName())) {
-              tableLocations.remove(e.getKey());
-              deletedSomething = true;
-            }
-          }
-        }
-        this.cachedServers.remove(serverName);
-      }
-      if (deletedSomething && LOG.isDebugEnabled()) {
-        LOG.debug("Removed all cached region locations that map to " + serverName);
-      }
-    }
-
-    /*
-     * @param tableName
-     * @return Map of cached locations for passed <code>tableName</code>
-     */
-    private ConcurrentSkipListMap<byte[], HRegionLocation> getTableLocations(
-        final TableName tableName) {
-      // find the map of cached locations for this table
-      ConcurrentSkipListMap<byte[], HRegionLocation> result;
-      result = this.cachedRegionLocations.get(tableName);
-      // if tableLocations for this table isn't built yet, make one
-      if (result == null) {
-        result = new ConcurrentSkipListMap<byte[], HRegionLocation>(Bytes.BYTES_COMPARATOR);
-        ConcurrentSkipListMap<byte[], HRegionLocation> old =
-            this.cachedRegionLocations.putIfAbsent(tableName, result);
-        if (old != null) {
-          return old;
-        }
-      }
-      return result;
+      metaCache.clearCache(serverName);
     }
 
     @Override
     public void clearRegionCache() {
-      this.cachedRegionLocations.clear();
-      this.cachedServers.clear();
+      metaCache.clearCache();
     }
 
     @Override
     public void clearRegionCache(final TableName tableName) {
-      this.cachedRegionLocations.remove(tableName);
+      metaCache.clearCache(tableName);
     }
 
     @Override
@@ -1274,37 +1196,7 @@ class ConnectionManager {
      */
     private void cacheLocation(final TableName tableName, final ServerName source,
         final HRegionLocation location) {
-      boolean isFromMeta = (source == null);
-      byte [] startKey = location.getRegionInfo().getStartKey();
-      ConcurrentMap<byte[], HRegionLocation> tableLocations = getTableLocations(tableName);
-      HRegionLocation oldLocation = tableLocations.putIfAbsent(startKey, location);
-      boolean isNewCacheEntry = (oldLocation == null);
-      if (isNewCacheEntry) {
-        cachedServers.add(location.getServerName());
-        return;
-      }
-      boolean updateCache;
-      // If the server in cache sends us a redirect, assume it's always valid.
-      if (oldLocation.getServerName().equals(source)) {
-        updateCache = true;
-      } else {
-        long newLocationSeqNum = location.getSeqNum();
-        // Meta record is stale - some (probably the same) server has closed the region
-        // with later seqNum and told us about the new location.
-        boolean isStaleMetaRecord = isFromMeta && (oldLocation.getSeqNum() > newLocationSeqNum);
-        // Same as above for redirect. However, in this case, if the number is equal to previous
-        // record, the most common case is that first the region was closed with seqNum, and then
-        // opened with the same seqNum; hence we will ignore the redirect.
-        // There are so many corner cases with various combinations of opens and closes that
-        // an additional counter on top of seqNum would be necessary to handle them all.
-        boolean isStaleRedirect = !isFromMeta && (oldLocation.getSeqNum() >= newLocationSeqNum);
-        boolean isStaleUpdate = (isStaleMetaRecord || isStaleRedirect);
-        updateCache = (!isStaleUpdate);
-      }
-      if (updateCache) {
-        tableLocations.replace(startKey, oldLocation, location);
-        cachedServers.add(location.getServerName());
-      }
+      metaCache.cacheLocation(tableName, source, location);
     }
 
     // Map keyed by service name + regionserver to service stub implementation
@@ -1987,7 +1879,7 @@ class ConnectionManager {
         }
       };
     }
- 
+
 
     private static void release(MasterServiceState mss) {
       if (mss != null && mss.connection != null) {
@@ -2046,37 +1938,17 @@ class ConnectionManager {
       cacheLocation(hri.getTable(), source, newHrl);
     }
 
-   /**
-    * Deletes the cached location of the region if necessary, based on some error from source.
-    * @param hri The region in question.
-    * @param source The source of the error that prompts us to invalidate cache.
-    */
-   void deleteCachedLocation(HRegionInfo hri, ServerName source) {
-     getTableLocations(hri.getTable()).remove(hri.getStartKey());
-   }
-
     @Override
     public void deleteCachedRegionLocation(final HRegionLocation location) {
-      if (location == null || location.getRegionInfo() == null) {
-        return;
-      }
-
-      HRegionLocation removedLocation;
-      TableName tableName = location.getRegionInfo().getTable();
-      Map<byte[], HRegionLocation> tableLocations = getTableLocations(tableName);
-      removedLocation = tableLocations.remove(location.getRegionInfo().getStartKey());
-      if (LOG.isDebugEnabled() && removedLocation != null) {
-        LOG.debug("Removed " +
-            location.getRegionInfo().getRegionNameAsString() +
-            " for tableName=" + tableName +
-            " from cache");
-      }
+      metaCache.clearCache(location);
     }
 
     @Override
     public void updateCachedLocations(final TableName tableName, byte[] rowkey,
-      final Object exception, final HRegionLocation source) {
-      updateCachedLocations(tableName, rowkey, exception, source.getServerName());
+        final Object exception, final HRegionLocation source) {
+      assert source != null;
+      updateCachedLocations(tableName, source.getRegionInfo().getRegionName()
+        , rowkey, exception, source.getServerName());
     }
 
     /**
@@ -2088,7 +1960,7 @@ class ConnectionManager {
      * @param source server that is the source of the location update.
      */
     @Override
-    public void updateCachedLocations(final TableName tableName, byte[] rowkey,
+    public void updateCachedLocations(final TableName tableName, byte[] regionName, byte[] rowkey,
       final Object exception, final ServerName source) {
       if (rowkey == null || tableName == null) {
         LOG.warn("Coding error, see method javadoc. row=" + (rowkey == null ? "null" : rowkey) +
@@ -2101,8 +1973,18 @@ class ConnectionManager {
         return;
       }
 
+      if (regionName == null) {
+        // we do not know which region, so just remove the cache entry for the row and server
+        metaCache.clearCache(tableName, rowkey, source);
+        return;
+      }
+
       // Is it something we have already updated?
-      final HRegionLocation oldLocation = getCachedLocation(tableName, rowkey);
+      final RegionLocations oldLocations = getCachedLocation(tableName, rowkey);
+      HRegionLocation oldLocation = null;
+      if (oldLocations != null) {
+        oldLocation = oldLocations.getRegionLocationByRegionName(regionName);
+      }
       if (oldLocation == null || !source.equals(oldLocation.getServerName())) {
         // There is no such location in the cache (it's been removed already) or
         // the cache has already been refreshed with a different location.  => nothing to do
@@ -2133,8 +2015,8 @@ class ConnectionManager {
       }
 
       // If we're here, it means that can cannot be sure about the location, so we remove it from
-      //  the cache.
-      deleteCachedLocation(regionInfo, source);
+      // the cache. Do not send the source because source can be a new server in the same host:port
+      metaCache.clearCache(regionInfo);
     }
 
     @Override
@@ -2221,24 +2103,9 @@ class ConnectionManager {
      * Return the number of cached region for a table. It will only be called
      * from a unit test.
      */
+    @VisibleForTesting
     int getNumberOfCachedRegionLocations(final TableName tableName) {
-      Map<byte[], HRegionLocation> tableLocs = this.cachedRegionLocations.get(tableName);
-      if (tableLocs == null) {
-        return 0;
-      }
-      return tableLocs.values().size();
-    }
-
-    /**
-     * Check the region cache to see whether a region is cached yet or not.
-     * Called by unit tests.
-     * @param tableName tableName
-     * @param row row
-     * @return Region cached or not.
-     */
-    boolean isRegionCached(TableName tableName, final byte[] row) {
-      HRegionLocation location = getCachedLocation(tableName, row);
-      return location != null;
+      return metaCache.getNumberOfCachedRegionLocations(tableName);
     }
 
     @Override
@@ -2567,7 +2434,7 @@ class ConnectionManager {
    * Look for an exception we know in the remote exception:
    * - hadoop.ipc wrapped exceptions
    * - nested exceptions
-   * 
+   *
    * Looks for: RegionMovedException / RegionOpeningException / RegionTooBusyException
    * @return null if we didn't find the exception, the exception otherwise.
    */

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnection.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnection.java
index 0305821..8e27dfd 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnection.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnection.java
@@ -27,11 +27,11 @@ import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.classification.InterfaceStability;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Abortable;
-import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.MasterNotRunningException;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.ZooKeeperConnectionException;
 import org.apache.hadoop.hbase.catalog.CatalogTracker;
 import org.apache.hadoop.hbase.client.coprocessor.Batch;
@@ -44,7 +44,7 @@ import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.MasterService;
  * keeps a cache of locations and then knows how to re-calibrate after they move.  You need one
  * of these to talk to your HBase cluster. {@link HConnectionManager} manages instances of this
  * class.  See it for how to get one of these.
- * 
+ *
  * <p>This is NOT a connection to a particular server but to ALL servers in the cluster.  Individual
  * connections are managed at a lower level.
  *
@@ -264,7 +264,8 @@ public interface HConnection extends Abortable, Closeable {
    * @return HRegionLocation that describes where to find the region in
    * question
    * @throws IOException if a remote or network exception occurs
-   * @deprecated internal method, do not use thru HConnection */
+   * @deprecated internal method, do not use thru HConnection
+   */
   @Deprecated
   public HRegionLocation locateRegion(final TableName tableName,
       final byte [] row) throws IOException;
@@ -323,12 +324,14 @@ public interface HConnection extends Abortable, Closeable {
    * Update the location cache. This is used internally by HBase, in most cases it should not be
    *  used by the client application.
    * @param tableName the table name
+   * @param regionName the regionName
    * @param rowkey the row
    * @param exception the exception if any. Can be null.
    * @param source the previous location
-   * @deprecated internal method, do not use thru HConnection */
+   * @deprecated internal method, do not use thru HConnection
+   */
   @Deprecated
-  void updateCachedLocations(TableName tableName, byte[] rowkey,
+  void updateCachedLocations(TableName tableName, byte[] regionName, byte[] rowkey,
                                     Object exception, ServerName source);
 
   @Deprecated
@@ -366,7 +369,8 @@ public interface HConnection extends Abortable, Closeable {
    *          regions from returned list.
    * @return list of region locations for all regions of table
    * @throws IOException
-   * @deprecated internal method, do not use thru HConnection */
+   * @deprecated internal method, do not use thru HConnection
+   */
   @Deprecated
   public List<HRegionLocation> locateRegions(final TableName tableName,
       final boolean useCache,
@@ -412,6 +416,7 @@ public interface HConnection extends Abortable, Closeable {
    * @throws IOException if a remote or network exception occurs
    * @deprecated You can pass master flag but nothing special is done.
    */
+  @Deprecated
   AdminService.BlockingInterface getAdmin(final ServerName serverName, boolean getMaster)
       throws IOException;
 
@@ -506,6 +511,7 @@ public interface HConnection extends Abortable, Closeable {
    * @throws IOException if a remote or network exception occurs
    * @deprecated This method will be changed from public to package protected.
    */
+  @Deprecated
   int getCurrentNrHRS() throws IOException;
 
   /**

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnectionManager.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnectionManager.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnectionManager.java
index f54d053..0d4f793 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnectionManager.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HConnectionManager.java
@@ -243,6 +243,7 @@ public class HConnectionManager {
    * @param conf configuration whose identity is used to find {@link HConnection} instance.
    * @deprecated
    */
+  @Deprecated
   public static void deleteConnection(Configuration conf) {
     ConnectionManager.deleteConnection(conf);
   }
@@ -254,6 +255,7 @@ public class HConnectionManager {
    * @param connection
    * @deprecated
    */
+  @Deprecated
   public static void deleteStaleConnection(HConnection connection) {
     ConnectionManager.deleteStaleConnection(connection);
   }
@@ -264,6 +266,7 @@ public class HConnectionManager {
    *  staleConnection to true.
    * @deprecated
    */
+  @Deprecated
   public static void deleteAllConnections(boolean staleConnection) {
     ConnectionManager.deleteAllConnections(staleConnection);
   }

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
index 13025b2..5cd0102 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java
@@ -43,7 +43,6 @@ import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.classification.InterfaceStability;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Cell;
-import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.HBaseConfiguration;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionInfo;
@@ -51,6 +50,7 @@ import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.KeyValueUtil;
+import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.AsyncProcess.AsyncRequestFuture;
@@ -76,11 +76,10 @@ import org.apache.hadoop.hbase.util.Threads;
 
 import com.google.protobuf.Descriptors;
 import com.google.protobuf.Message;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.protobuf.Service;
 import com.google.protobuf.ServiceException;
 
-import com.google.common.annotations.VisibleForTesting;
-
 /**
  * <p>Used to communicate with a single HBase table.  An implementation of
  * {@link HTableInterface}.  Instances of this class can be constructed directly but it is
@@ -608,12 +607,15 @@ public class HTable implements HTableInterface {
    * @return Pair of arrays of region starting and ending row keys
    * @throws IOException if a remote or network exception occurs
    */
+  // TODO: these are not in HTableInterface. Should we add them there or move these to HBaseAdmin?
   public Pair<byte[][],byte[][]> getStartEndKeys() throws IOException {
-    NavigableMap<HRegionInfo, ServerName> regions = getRegionLocations();
+
+    List<RegionLocations> regions = listRegionLocations();
     final List<byte[]> startKeyList = new ArrayList<byte[]>(regions.size());
     final List<byte[]> endKeyList = new ArrayList<byte[]>(regions.size());
 
-    for (HRegionInfo region : regions.keySet()) {
+    for (RegionLocations locations : regions) {
+      HRegionInfo region = locations.getRegionLocation().getRegionInfo();
       startKeyList.add(region.getStartKey());
       endKeyList.add(region.getEndKey());
     }
@@ -623,13 +625,20 @@ public class HTable implements HTableInterface {
       endKeyList.toArray(new byte[endKeyList.size()][]));
   }
 
+  @VisibleForTesting
+  List<RegionLocations> listRegionLocations() throws IOException {
+    return MetaScanner.listTableRegionLocations(getConfiguration(), this.connection, getName());
+  }
+
   /**
    * Gets all the regions and their address for this table.
    * <p>
    * This is mainly useful for the MapReduce integration.
    * @return A map of HRegionInfo with it's server address
    * @throws IOException if a remote or network exception occurs
+   * @deprecated This is no longer a public API
    */
+  @Deprecated
   public NavigableMap<HRegionInfo, ServerName> getRegionLocations() throws IOException {
     // TODO: Odd that this returns a Map of HRI to SN whereas getRegionLocation, singular, returns an HRegionLocation.
     return MetaScanner.allTableRegions(getConfiguration(), this.connection, getName(), false);
@@ -643,7 +652,9 @@ public class HTable implements HTableInterface {
    * @return A list of HRegionLocations corresponding to the regions that
    * contain the specified range
    * @throws IOException if a remote or network exception occurs
+   * @deprecated This is no longer a public API
    */
+  @Deprecated
   public List<HRegionLocation> getRegionsInRange(final byte [] startKey,
     final byte [] endKey) throws IOException {
     return getRegionsInRange(startKey, endKey, false);
@@ -658,7 +669,9 @@ public class HTable implements HTableInterface {
    * @return A list of HRegionLocations corresponding to the regions that
    * contain the specified range
    * @throws IOException if a remote or network exception occurs
+   * @deprecated This is no longer a public API
    */
+  @Deprecated
   public List<HRegionLocation> getRegionsInRange(final byte [] startKey,
       final byte [] endKey, final boolean reload) throws IOException {
     return getKeysAndRegionsInRange(startKey, endKey, false, reload).getSecond();
@@ -674,7 +687,9 @@ public class HTable implements HTableInterface {
    * @return A pair of list of start keys and list of HRegionLocations that
    *         contain the specified range
    * @throws IOException if a remote or network exception occurs
+   * @deprecated This is no longer a public API
    */
+  @Deprecated
   private Pair<List<byte[]>, List<HRegionLocation>> getKeysAndRegionsInRange(
       final byte[] startKey, final byte[] endKey, final boolean includeEndKey)
       throws IOException {
@@ -692,7 +707,9 @@ public class HTable implements HTableInterface {
    * @return A pair of list of start keys and list of HRegionLocations that
    *         contain the specified range
    * @throws IOException if a remote or network exception occurs
+   * @deprecated This is no longer a public API
    */
+  @Deprecated
   private Pair<List<byte[]>, List<HRegionLocation>> getKeysAndRegionsInRange(
       final byte[] startKey, final byte[] endKey, final boolean includeEndKey,
       final boolean reload) throws IOException {
@@ -727,7 +744,8 @@ public class HTable implements HTableInterface {
        throws IOException {
      RegionServerCallable<Result> callable = new RegionServerCallable<Result>(this.connection,
          tableName, row) {
-       public Result call(int callTimeout) throws IOException {
+       @Override
+      public Result call(int callTimeout) throws IOException {
          PayloadCarryingRpcController controller = rpcControllerFactory.newController();
          controller.setPriority(tableName);
          controller.setCallTimeout(callTimeout);
@@ -801,6 +819,7 @@ public class HTable implements HTableInterface {
   public Result get(final Get get) throws IOException {
     RegionServerCallable<Result> callable = new RegionServerCallable<Result>(this.connection,
         getName(), get.getRow()) {
+      @Override
       public Result call(int callTimeout) throws IOException {
         ClientProtos.GetRequest request =
             RequestConverter.buildGetRequest(getLocation().getRegionInfo().getRegionName(), get);
@@ -862,6 +881,7 @@ public class HTable implements HTableInterface {
    * @deprecated If any exception is thrown by one of the actions, there is no way to
    * retrieve the partially executed results. Use {@link #batch(List, Object[])} instead.
    */
+  @Deprecated
   @Override
   public Object[] batch(final List<? extends Row> actions)
      throws InterruptedException, IOException {
@@ -887,6 +907,7 @@ public class HTable implements HTableInterface {
    * {@link #batchCallback(List, Object[], org.apache.hadoop.hbase.client.coprocessor.Batch.Callback)}
    * instead.
    */
+  @Deprecated
   @Override
   public <R> Object[] batchCallback(
     final List<? extends Row> actions, final Batch.Callback<R> callback) throws IOException,
@@ -904,6 +925,7 @@ public class HTable implements HTableInterface {
   throws IOException {
     RegionServerCallable<Boolean> callable = new RegionServerCallable<Boolean>(connection,
         tableName, delete.getRow()) {
+      @Override
       public Boolean call(int callTimeout) throws IOException {
         PayloadCarryingRpcController controller = rpcControllerFactory.newController();
         controller.setPriority(tableName);
@@ -1044,6 +1066,7 @@ public class HTable implements HTableInterface {
   public void mutateRow(final RowMutations rm) throws IOException {
     RegionServerCallable<Void> callable =
         new RegionServerCallable<Void>(connection, getName(), rm.getRow()) {
+      @Override
       public Void call(int callTimeout) throws IOException {
         PayloadCarryingRpcController controller = rpcControllerFactory.newController();
         controller.setPriority(tableName);
@@ -1078,6 +1101,7 @@ public class HTable implements HTableInterface {
     final long nonceGroup = ng.getNonceGroup(), nonce = ng.newNonce();
     RegionServerCallable<Result> callable =
       new RegionServerCallable<Result>(this.connection, getName(), append.getRow()) {
+        @Override
         public Result call(int callTimeout) throws IOException {
           PayloadCarryingRpcController controller = rpcControllerFactory.newController();
           controller.setPriority(getTableName());
@@ -1109,6 +1133,7 @@ public class HTable implements HTableInterface {
     final long nonceGroup = ng.getNonceGroup(), nonce = ng.newNonce();
     RegionServerCallable<Result> callable = new RegionServerCallable<Result>(this.connection,
         getName(), increment.getRow()) {
+      @Override
       public Result call(int callTimeout) throws IOException {
         PayloadCarryingRpcController controller = rpcControllerFactory.newController();
         controller.setPriority(getTableName());
@@ -1172,6 +1197,7 @@ public class HTable implements HTableInterface {
     final long nonceGroup = ng.getNonceGroup(), nonce = ng.newNonce();
     RegionServerCallable<Long> callable =
       new RegionServerCallable<Long>(connection, getName(), row) {
+        @Override
         public Long call(int callTimeout) throws IOException {
           PayloadCarryingRpcController controller = rpcControllerFactory.newController();
           controller.setPriority(getTableName());
@@ -1202,6 +1228,7 @@ public class HTable implements HTableInterface {
   throws IOException {
     RegionServerCallable<Boolean> callable =
       new RegionServerCallable<Boolean>(connection, getName(), row) {
+        @Override
         public Boolean call(int callTimeout) throws IOException {
           PayloadCarryingRpcController controller = rpcControllerFactory.newController();
           controller.setPriority(tableName);
@@ -1230,6 +1257,7 @@ public class HTable implements HTableInterface {
   throws IOException {
     RegionServerCallable<Boolean> callable =
       new RegionServerCallable<Boolean>(connection, getName(), row) {
+        @Override
         public Boolean call(int callTimeout) throws IOException {
           PayloadCarryingRpcController controller = new PayloadCarryingRpcController();
           controller.setPriority(tableName);
@@ -1259,6 +1287,7 @@ public class HTable implements HTableInterface {
   throws IOException {
     RegionServerCallable<Boolean> callable =
       new RegionServerCallable<Boolean>(connection, getName(), row) {
+        @Override
         public Boolean call(int callTimeout) throws IOException {
           PayloadCarryingRpcController controller = rpcControllerFactory.newController();
           controller.setPriority(tableName);
@@ -1287,6 +1316,7 @@ public class HTable implements HTableInterface {
   throws IOException {
     RegionServerCallable<Boolean> callable =
       new RegionServerCallable<Boolean>(connection, getName(), row) {
+        @Override
         public Boolean call(int callTimeout) throws IOException {
           PayloadCarryingRpcController controller = rpcControllerFactory.newController();
           controller.setPriority(tableName);
@@ -1474,6 +1504,7 @@ public class HTable implements HTableInterface {
    * @param writeBufferSize The new write buffer size, in bytes.
    * @throws IOException if a remote or network exception occurs.
    */
+  @Override
   public void setWriteBufferSize(long writeBufferSize) throws IOException {
     this.writeBufferSize = writeBufferSize;
     if(currentWriteBufferSize > writeBufferSize) {
@@ -1595,6 +1626,7 @@ public class HTable implements HTableInterface {
   /**
    * {@inheritDoc}
    */
+  @Override
   public CoprocessorRpcChannel coprocessorService(byte[] row) {
     return new RegionCoprocessorRpcChannel(connection, tableName, row);
   }
@@ -1609,6 +1641,7 @@ public class HTable implements HTableInterface {
     final Map<byte[],R> results =  Collections.synchronizedMap(
         new TreeMap<byte[], R>(Bytes.BYTES_COMPARATOR));
     coprocessorService(service, startKey, endKey, callable, new Batch.Callback<R>() {
+      @Override
       public void update(byte[] region, byte[] row, R value) {
         if (region != null) {
           results.put(region, value);
@@ -1636,6 +1669,7 @@ public class HTable implements HTableInterface {
           new RegionCoprocessorRpcChannel(connection, tableName, r);
       Future<R> future = pool.submit(
           new Callable<R>() {
+            @Override
             public R call() throws Exception {
               T instance = ProtobufUtil.newServiceStub(service, channel);
               R result = callable.call(instance);

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java
new file mode 100644
index 0000000..b8cd429
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java
@@ -0,0 +1,343 @@
+/**
+ * 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 java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.RegionLocations;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * A cache implementation for region locations from meta.
+ */
+@InterfaceAudience.Private
+public class MetaCache {
+
+  private static final Log LOG = LogFactory.getLog(MetaCache.class);
+
+  /**
+   * Map of table to table {@link HRegionLocation}s.
+   */
+  private final ConcurrentMap<TableName, ConcurrentSkipListMap<byte[], RegionLocations>>
+  cachedRegionLocations =
+  new ConcurrentHashMap<TableName, ConcurrentSkipListMap<byte[], RegionLocations>>();
+
+  // The presence of a server in the map implies it's likely that there is an
+  // entry in cachedRegionLocations that map to this server; but the absence
+  // of a server in this map guarentees that there is no entry in cache that
+  // maps to the absent server.
+  // The access to this attribute must be protected by a lock on cachedRegionLocations
+  private final Set<ServerName> cachedServers = new ConcurrentSkipListSet<ServerName>();
+
+  /**
+   * Search the cache for a location that fits our table and row key.
+   * Return null if no suitable region is located.
+   *
+   * @param tableName
+   * @param row
+   * @return Null or region location found in cache.
+   */
+  public RegionLocations getCachedLocation(final TableName tableName, final byte [] row) {
+    ConcurrentSkipListMap<byte[], RegionLocations> tableLocations =
+      getTableLocations(tableName);
+
+    Entry<byte[], RegionLocations> e = tableLocations.floorEntry(row);
+    if (e == null) {
+      return null;
+    }
+    RegionLocations possibleRegion = e.getValue();
+
+    // make sure that the end key is greater than the row we're looking
+    // for, otherwise the row actually belongs in the next region, not
+    // this one. the exception case is when the endkey is
+    // HConstants.EMPTY_END_ROW, signifying that the region we're
+    // checking is actually the last region in the table.
+    byte[] endKey = possibleRegion.getRegionLocation().getRegionInfo().getEndKey();
+    if (Bytes.equals(endKey, HConstants.EMPTY_END_ROW) ||
+        tableName.getRowComparator().compareRows(
+            endKey, 0, endKey.length, row, 0, row.length) > 0) {
+      return possibleRegion;
+    }
+
+    // Passed all the way through, so we got nothing - complete cache miss
+    return null;
+  }
+
+  /**
+   * Put a newly discovered HRegionLocation into the cache.
+   * @param tableName The table name.
+   * @param source the source of the new location
+   * @param location the new location
+   */
+  public void cacheLocation(final TableName tableName, final ServerName source,
+      final HRegionLocation location) {
+    assert source != null;
+    byte [] startKey = location.getRegionInfo().getStartKey();
+    ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
+    RegionLocations locations = new RegionLocations(new HRegionLocation[] {location}) ;
+    RegionLocations oldLocations = tableLocations.putIfAbsent(startKey, locations);
+    boolean isNewCacheEntry = (oldLocations == null);
+    if (isNewCacheEntry) {
+      addToCachedServers(locations);
+      return;
+    }
+
+    // If the server in cache sends us a redirect, assume it's always valid.
+    HRegionLocation oldLocation = oldLocations.getRegionLocation(
+      location.getRegionInfo().getReplicaId());
+    boolean force = oldLocation != null && oldLocation.getServerName() != null
+        && oldLocation.getServerName().equals(source);
+
+    // For redirect if the number is equal to previous
+    // record, the most common case is that first the region was closed with seqNum, and then
+    // opened with the same seqNum; hence we will ignore the redirect.
+    // There are so many corner cases with various combinations of opens and closes that
+    // an additional counter on top of seqNum would be necessary to handle them all.
+    RegionLocations updatedLocations = oldLocations.updateLocation(location, false, force);
+    if (oldLocations != updatedLocations) {
+      tableLocations.replace(startKey, oldLocations, updatedLocations);
+      addToCachedServers(updatedLocations);
+    }
+  }
+
+  /**
+   * Put a newly discovered HRegionLocation into the cache.
+   * @param tableName The table name.
+   * @param location the new location
+   */
+  public void cacheLocation(final TableName tableName, final RegionLocations location) {
+    byte [] startKey = location.getRegionLocation().getRegionInfo().getStartKey();
+    ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
+    RegionLocations oldLocation = tableLocations.putIfAbsent(startKey, location);
+    boolean isNewCacheEntry = (oldLocation == null);
+    if (isNewCacheEntry) {
+      addToCachedServers(location);
+      return;
+    }
+
+    // merge old and new locations and add it to the cache
+    // Meta record might be stale - some (probably the same) server has closed the region
+    // with later seqNum and told us about the new location.
+    RegionLocations mergedLocation = oldLocation.mergeLocations(location);
+    tableLocations.replace(startKey, oldLocation, mergedLocation);
+    addToCachedServers(location);
+  }
+
+  private void addToCachedServers(RegionLocations locations) {
+    for (HRegionLocation loc : locations.getRegionLocations()) {
+      if (loc != null) {
+        cachedServers.add(loc.getServerName());
+      }
+    }
+  }
+
+  /**
+   * @param tableName
+   * @return Map of cached locations for passed <code>tableName</code>
+   */
+  private ConcurrentSkipListMap<byte[], RegionLocations>
+    getTableLocations(final TableName tableName) {
+    // find the map of cached locations for this table
+    ConcurrentSkipListMap<byte[], RegionLocations> result;
+    result = this.cachedRegionLocations.get(tableName);
+    // if tableLocations for this table isn't built yet, make one
+    if (result == null) {
+      result = new ConcurrentSkipListMap<byte[], RegionLocations>(Bytes.BYTES_COMPARATOR);
+      ConcurrentSkipListMap<byte[], RegionLocations> old =
+          this.cachedRegionLocations.putIfAbsent(tableName, result);
+      if (old != null) {
+        return old;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Check the region cache to see whether a region is cached yet or not.
+   * @param tableName tableName
+   * @param row row
+   * @return Region cached or not.
+   */
+  public boolean isRegionCached(TableName tableName, final byte[] row) {
+    RegionLocations location = getCachedLocation(tableName, row);
+    return location != null;
+  }
+
+  /**
+   * Return the number of cached region for a table. It will only be called
+   * from a unit test.
+   */
+  public int getNumberOfCachedRegionLocations(final TableName tableName) {
+    Map<byte[], RegionLocations> tableLocs = this.cachedRegionLocations.get(tableName);
+    if (tableLocs == null) {
+      return 0;
+    }
+    int numRegions = 0;
+    for (RegionLocations tableLoc : tableLocs.values()) {
+      numRegions += tableLoc.numNonNullElements();
+    }
+    return numRegions;
+  }
+
+  /**
+   * Delete all cached entries.
+   */
+  public void clearCache() {
+    this.cachedRegionLocations.clear();
+    this.cachedServers.clear();
+  }
+
+  /**
+   * Delete all cached entries of a server.
+   */
+  public void clearCache(final ServerName serverName) {
+    if (!this.cachedServers.contains(serverName)) {
+      return;
+    }
+
+    boolean deletedSomething = false;
+    synchronized (this.cachedServers) {
+      // We block here, because if there is an error on a server, it's likely that multiple
+      //  threads will get the error  simultaneously. If there are hundreds of thousand of
+      //  region location to check, it's better to do this only once. A better pattern would
+      //  be to check if the server is dead when we get the region location.
+      if (!this.cachedServers.contains(serverName)) {
+        return;
+      }
+      for (ConcurrentMap<byte[], RegionLocations> tableLocations : cachedRegionLocations.values()){
+        for (Entry<byte[], RegionLocations> e : tableLocations.entrySet()) {
+          RegionLocations regionLocations = e.getValue();
+          if (regionLocations != null) {
+            RegionLocations updatedLocations = regionLocations.removeByServer(serverName);
+            deletedSomething |= regionLocations == updatedLocations;
+            if (updatedLocations != regionLocations) {
+              if (updatedLocations.isEmpty()) {
+                tableLocations.remove(e.getKey(), regionLocations);
+              } else {
+                tableLocations.replace(e.getKey(), regionLocations, updatedLocations);
+              }
+            }
+          }
+        }
+      }
+      this.cachedServers.remove(serverName);
+    }
+    if (deletedSomething && LOG.isDebugEnabled()) {
+      LOG.debug("Removed all cached region locations that map to " + serverName);
+    }
+  }
+
+  /**
+   * Delete all cached entries of a table.
+   */
+  public void clearCache(final TableName tableName) {
+    this.cachedRegionLocations.remove(tableName);
+  }
+
+  /**
+   * Delete a cached location, no matter what it is. Called when we were told to not use cache.
+   * @param tableName tableName
+   * @param row
+   */
+  public void clearCache(final TableName tableName, final byte [] row) {
+    ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
+
+    RegionLocations regionLocations = getCachedLocation(tableName, row);
+    if (regionLocations != null) {
+      byte[] startKey = regionLocations.getRegionLocation().getRegionInfo().getStartKey();
+      boolean removed = tableLocations.remove(startKey, regionLocations);
+      if (removed && LOG.isDebugEnabled()) {
+        LOG.debug("Removed " + regionLocations + " from cache");
+      }
+    }
+  }
+
+  /**
+   * Delete a cached location for a table, row and server
+   */
+  public void clearCache(final TableName tableName, final byte [] row, ServerName serverName) {
+    ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
+
+    RegionLocations regionLocations = getCachedLocation(tableName, row);
+    if (regionLocations != null) {
+      RegionLocations updatedLocations = regionLocations.removeByServer(serverName);
+      if (updatedLocations != regionLocations) {
+        byte[] startKey = regionLocations.getRegionLocation().getRegionInfo().getStartKey();
+        if (updatedLocations.isEmpty()) {
+          tableLocations.remove(startKey, regionLocations);
+        } else {
+          tableLocations.replace(startKey, regionLocations, updatedLocations);
+        }
+      }
+    }
+  }
+
+  /**
+   * Deletes the cached location of the region if necessary, based on some error from source.
+   * @param hri The region in question.
+   */
+  public void clearCache(HRegionInfo hri) {
+    ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(hri.getTable());
+    RegionLocations regionLocations = tableLocations.get(hri.getStartKey());
+    if (regionLocations != null) {
+      HRegionLocation oldLocation = regionLocations.getRegionLocation(hri.getReplicaId());
+      RegionLocations updatedLocations = regionLocations.remove(oldLocation);
+      if (updatedLocations != regionLocations) {
+        if (updatedLocations.isEmpty()) {
+          tableLocations.remove(hri.getStartKey(), regionLocations);
+        } else {
+          tableLocations.replace(hri.getStartKey(), regionLocations, updatedLocations);
+        }
+      }
+    }
+  }
+
+  public void clearCache(final HRegionLocation location) {
+    if (location == null) {
+      return;
+    }
+
+    TableName tableName = location.getRegionInfo().getTable();
+    ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
+    RegionLocations rll = tableLocations.get(location.getRegionInfo().getStartKey());
+    RegionLocations updatedLocations = rll.remove(location);
+    if (updatedLocations.isEmpty()) {
+      tableLocations.remove(location.getRegionInfo().getStartKey(), rll);
+    }
+    if (LOG.isDebugEnabled() && (rll == updatedLocations)) {
+      LOG.debug("Removed " +
+          location.getRegionInfo().getRegionNameAsString() +
+          " for tableName=" + tableName +
+          " from cache");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaScanner.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaScanner.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaScanner.java
index b4d3268..1442cbf 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaScanner.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaScanner.java
@@ -30,11 +30,14 @@ 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.TableName;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.TableNotFoundException;
+import org.apache.hadoop.hbase.catalog.MetaReader;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.ExceptionUtil;
 
@@ -50,6 +53,7 @@ import org.apache.hadoop.hbase.util.ExceptionUtil;
  * see HBASE-5986, and {@link DefaultMetaScannerVisitor} for details. </p>
  */
 @InterfaceAudience.Private
+//TODO: merge this to MetaReader, get rid of it.
 public class MetaScanner {
   private static final Log LOG = LogFactory.getLog(MetaScanner.class);
   /**
@@ -216,14 +220,14 @@ public class MetaScanner {
    * table Result.
    * @param data a Result object from the catalog table scan
    * @return HRegionInfo or null
+   * @deprecated Use {@link MetaReader#getRegionLocations(Result)}
    */
+  @Deprecated
   public static HRegionInfo getHRegionInfo(Result data) {
     return HRegionInfo.getHRegionInfo(data);
   }
 
   /**
-   * Used in tests.
-   *
    * Lists all of the regions currently in META.
    * @param conf
    * @param offlined True if we are to include offlined regions, false and we'll
@@ -234,22 +238,23 @@ public class MetaScanner {
   public static List<HRegionInfo> listAllRegions(Configuration conf, final boolean offlined)
   throws IOException {
     final List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
-    MetaScannerVisitor visitor = new DefaultMetaScannerVisitor() {
+    MetaScannerVisitor visitor = new MetaScannerVisitorBase() {
         @Override
-        public boolean processRowInternal(Result result) throws IOException {
+        public boolean processRow(Result result) throws IOException {
           if (result == null || result.isEmpty()) {
             return true;
           }
 
-          HRegionInfo regionInfo = getHRegionInfo(result);
-          if (regionInfo == null) {
-            LOG.warn("Null REGIONINFO_QUALIFIER: " + result);
-            return true;
+          RegionLocations locations = MetaReader.getRegionLocations(result);
+          if (locations == null) return true;
+          for (HRegionLocation loc : locations.getRegionLocations()) {
+            if (loc != null) {
+              HRegionInfo regionInfo = loc.getRegionInfo();
+              // If region offline AND we are not to include offlined regions, return.
+              if (regionInfo.isOffline() && !offlined) continue;
+              regions.add(regionInfo);
+            }
           }
-
-          // If region offline AND we are not to include offlined regions, return.
-          if (regionInfo.isOffline() && !offlined) return true;
-          regions.add(regionInfo);
           return true;
         }
     };
@@ -272,10 +277,34 @@ public class MetaScanner {
       new TreeMap<HRegionInfo, ServerName>();
     MetaScannerVisitor visitor = new TableMetaScannerVisitor(tableName) {
       @Override
-      public boolean processRowInternal(Result rowResult) throws IOException {
-        HRegionInfo info = getHRegionInfo(rowResult);
-        ServerName serverName = HRegionInfo.getServerName(rowResult);
-        regions.put(new UnmodifyableHRegionInfo(info), serverName);
+      public boolean processRowInternal(Result result) throws IOException {
+        RegionLocations locations = MetaReader.getRegionLocations(result);
+        if (locations == null) return true;
+        for (HRegionLocation loc : locations.getRegionLocations()) {
+          if (loc != null) {
+            HRegionInfo regionInfo = loc.getRegionInfo();
+            regions.put(new UnmodifyableHRegionInfo(regionInfo), loc.getServerName());
+          }
+        }
+        return true;
+      }
+    };
+    metaScan(conf, connection, visitor, tableName);
+    return regions;
+  }
+
+  /**
+   * Lists table regions and locations grouped by region range from META.
+   */
+  public static List<RegionLocations> listTableRegionLocations(Configuration conf,
+      ClusterConnection connection, final TableName tableName) throws IOException {
+    final List<RegionLocations> regions = new ArrayList<RegionLocations>();
+    MetaScannerVisitor visitor = new TableMetaScannerVisitor(tableName) {
+      @Override
+      public boolean processRowInternal(Result result) throws IOException {
+        RegionLocations locations = MetaReader.getRegionLocations(result);
+        if (locations == null) return true;
+        regions.add(locations);
         return true;
       }
     };

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java
new file mode 100644
index 0000000..abe9bf5
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionReplicaUtil.java
@@ -0,0 +1,65 @@
+/**
+ * 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.HRegionInfo;
+
+/**
+ * Utility methods which contain the logic for regions and replicas.
+ */
+@InterfaceAudience.Private
+public class RegionReplicaUtil {
+
+  /**
+   * The default replicaId for the region
+   */
+  private static final int DEFAULT_REPLICA_ID = 0;
+
+  /**
+   * Returns the HRegionInfo for the given replicaId. HRegionInfo's correspond to
+   * a range of a table, but more than one "instance" of the same range can be
+   * deployed which are differentiated by the replicaId.
+   * @param replicaId the replicaId to use
+   * @return an HRegionInfo object corresponding to the same range (table, start and
+   * end key), but for the given replicaId.
+   */
+  public static HRegionInfo getRegionInfoForReplica(HRegionInfo regionInfo, int replicaId) {
+    if (regionInfo.getReplicaId() == replicaId) {
+      return regionInfo;
+    }
+    HRegionInfo replicaInfo = new HRegionInfo(regionInfo.getTable(), regionInfo.getStartKey(),
+      regionInfo.getEndKey(), regionInfo.isSplit(), regionInfo.getRegionId(), replicaId);
+
+    replicaInfo.setOffline(regionInfo.isOffline());
+    return replicaInfo;
+  }
+
+  /**
+   * Returns the HRegionInfo for the default replicaId (0). HRegionInfo's correspond to
+   * a range of a table, but more than one "instance" of the same range can be
+   * deployed which are differentiated by the replicaId.
+   * @return an HRegionInfo object corresponding to the same range (table, start and
+   * end key), but for the default replicaId.
+   */
+  public static HRegionInfo getRegionInfoForDefaultReplica(HRegionInfo regionInfo) {
+    return getRegionInfoForReplica(regionInfo, DEFAULT_REPLICA_ID);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionServerCallable.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionServerCallable.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionServerCallable.java
index 1740a2d..1cefb7f 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionServerCallable.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RegionServerCallable.java
@@ -73,6 +73,7 @@ public abstract class RegionServerCallable<T> implements RetryingCallable<T> {
    * @param reload Set this to true if connection should re-find the region
    * @throws IOException e
    */
+  @Override
   public void prepare(final boolean reload) throws IOException {
     this.location = connection.getRegionLocation(tableName, row, reload);
     if (this.location == null) {
@@ -124,7 +125,7 @@ public abstract class RegionServerCallable<T> implements RetryingCallable<T> {
       // hbase:meta again to find the new location
       if (this.location != null) getConnection().clearCaches(location.getServerName());
     } else if (t instanceof RegionMovedException) {
-      getConnection().updateCachedLocations(tableName, row, t, location.getServerName());
+      getConnection().updateCachedLocations(tableName, row, t, location);
     } else if (t instanceof NotServingRegionException && !retrying) {
       // Purge cache entries for this specific region from hbase:meta cache
       // since we don't call connect(true) when number of retries is 1.

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Registry.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Registry.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Registry.java
index 2890cf4..aab547e 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Registry.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Registry.java
@@ -19,8 +19,8 @@ package org.apache.hadoop.hbase.client;
 
 import java.io.IOException;
 
+import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.HRegionLocation;
 
 /**
  * Cluster registry.
@@ -36,7 +36,7 @@ interface Registry {
    * @return Meta region location
    * @throws IOException
    */
-  HRegionLocation getMetaRegionLocation() throws IOException;
+  RegionLocations getMetaRegionLocation() throws IOException;
 
   /**
    * @return Cluster id.

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZooKeeperRegistry.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZooKeeperRegistry.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZooKeeperRegistry.java
index 529b7f6..ca7ce68 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZooKeeperRegistry.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZooKeeperRegistry.java
@@ -22,10 +22,11 @@ import java.io.InterruptedIOException;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.zookeeper.MetaRegionTracker;
 import org.apache.hadoop.hbase.zookeeper.ZKClusterId;
 import org.apache.hadoop.hbase.zookeeper.ZKTableStateClientSideReader;
@@ -49,7 +50,7 @@ class ZooKeeperRegistry implements Registry {
   }
 
   @Override
-  public HRegionLocation getMetaRegionLocation() throws IOException {
+  public RegionLocations getMetaRegionLocation() throws IOException {
     ZooKeeperKeepAliveConnection zkw = hci.getKeepAliveZooKeeperWatcher();
 
     try {
@@ -62,7 +63,8 @@ class ZooKeeperRegistry implements Registry {
           "; serverName=" + ((servername == null) ? "null" : servername));
       }
       if (servername == null) return null;
-      return new HRegionLocation(HRegionInfo.FIRST_META_REGIONINFO, servername, 0);
+      HRegionLocation loc = new HRegionLocation(HRegionInfo.FIRST_META_REGIONINFO, servername, 0);
+      return new RegionLocations(new HRegionLocation[] {loc});
     } catch (InterruptedException e) {
       Thread.currentThread().interrupt();
       return null;

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/test/java/org/apache/hadoop/hbase/TestRegionLocations.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/TestRegionLocations.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/TestRegionLocations.java
new file mode 100644
index 0000000..603f8d5
--- /dev/null
+++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/TestRegionLocations.java
@@ -0,0 +1,275 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class TestRegionLocations {
+
+  ServerName sn0 = ServerName.valueOf("host0", 10, 10);
+  ServerName sn1 = ServerName.valueOf("host1", 10, 10);
+  ServerName sn2 = ServerName.valueOf("host2", 10, 10);
+  ServerName sn3 = ServerName.valueOf("host3", 10, 10);
+
+  HRegionInfo info0 = hri(0);
+  HRegionInfo info1 = hri(1);
+  HRegionInfo info2 = hri(2);
+  HRegionInfo info9 = hri(9);
+
+  @Test
+  public void testSizeMethods() {
+    RegionLocations list = new RegionLocations();
+    assertTrue(list.isEmpty());
+    assertEquals(0, list.size());
+    assertEquals(0, list.numNonNullElements());
+
+    list = hrll((HRegionLocation)null);
+    assertTrue(list.isEmpty());
+    assertEquals(0, list.size());
+    assertEquals(0, list.numNonNullElements());
+
+    HRegionInfo info0 = hri(0);
+    list = hrll(hrl(info0, null));
+    assertFalse(list.isEmpty());
+    assertEquals(1, list.size());
+    assertEquals(1, list.numNonNullElements());
+
+    HRegionInfo info9 = hri(9);
+    list = hrll(hrl(info9, null));
+    assertFalse(list.isEmpty());
+    assertEquals(10, list.size());
+    assertEquals(1, list.numNonNullElements());
+
+    list = hrll(hrl(info0, null), hrl(info9, null));
+    assertFalse(list.isEmpty());
+    assertEquals(10, list.size());
+    assertEquals(2, list.numNonNullElements());
+  }
+
+  private HRegionInfo hri(int replicaId) {
+    TableName table = TableName.valueOf("table");
+    byte[] startKey = HConstants.EMPTY_START_ROW;
+    byte[] endKey = HConstants.EMPTY_END_ROW;
+    long regionId = System.currentTimeMillis();
+    HRegionInfo info = new HRegionInfo(table, startKey, endKey, false, regionId, replicaId);
+    return info;
+  }
+
+  private HRegionLocation hrl(HRegionInfo hri, ServerName sn) {
+    return new HRegionLocation(hri, sn);
+  }
+
+  private HRegionLocation hrl(HRegionInfo hri, ServerName sn, long seqNum) {
+    return new HRegionLocation(hri, sn, seqNum);
+  }
+
+  private RegionLocations hrll(HRegionLocation ... locations) {
+    return new RegionLocations(locations);
+  }
+
+  @Test
+  public void testRemoveByServer() {
+    RegionLocations list;
+
+    // test remove from empty list
+    list = new RegionLocations();
+    assertTrue(list == list.removeByServer(sn0));
+
+    // test remove from single element list
+    list = hrll(hrl(info0, sn0));
+    assertTrue(list == list.removeByServer(sn1));
+    list = list.removeByServer(sn0);
+    assertTrue(list.isEmpty());
+
+    // test remove from multi element list
+    list = hrll(hrl(info0, sn0), hrl(info1, sn1), hrl(info2, sn2), hrl(info9, sn2));
+    assertTrue(list == list.removeByServer(sn3)); // no region is mapped to sn3
+    list = list.removeByServer(sn0);
+    assertNull(list.getRegionLocation(0));
+    assertEquals(sn1, list.getRegionLocation(1).getServerName());
+    assertEquals(sn2, list.getRegionLocation(2).getServerName());
+    assertNull(list.getRegionLocation(5));
+    assertEquals(sn2, list.getRegionLocation(9).getServerName());
+
+    // test multi-element remove from multi element list
+    list = hrll(hrl(info0, sn1), hrl(info1, sn1), hrl(info2, sn0), hrl(info9, sn0));
+    list = list.removeByServer(sn0);
+    assertEquals(sn1, list.getRegionLocation(0).getServerName());
+    assertEquals(sn1, list.getRegionLocation(1).getServerName());
+    assertNull(list.getRegionLocation(2));
+    assertNull(list.getRegionLocation(5));
+    assertNull(list.getRegionLocation(9));
+  }
+
+  @Test
+  public void testRemove() {
+    RegionLocations list;
+
+    // test remove from empty list
+    list = new RegionLocations();
+    assertTrue(list == list.remove(hrl(info0, sn0)));
+
+    // test remove from single element list
+    list = hrll(hrl(info0, sn0));
+    assertTrue(list == list.remove(hrl(info0, sn1)));
+    list = list.remove(hrl(info0, sn0));
+    assertTrue(list.isEmpty());
+
+    // test remove from multi element list
+    list = hrll(hrl(info0, sn0), hrl(info1, sn1), hrl(info2, sn2), hrl(info9, sn2));
+    assertTrue(list == list.remove(hrl(info1, sn3))); // no region is mapped to sn3
+    list = list.remove(hrl(info0, sn0));
+    assertNull(list.getRegionLocation(0));
+    assertEquals(sn1, list.getRegionLocation(1).getServerName());
+    assertEquals(sn2, list.getRegionLocation(2).getServerName());
+    assertNull(list.getRegionLocation(5));
+    assertEquals(sn2, list.getRegionLocation(9).getServerName());
+
+    list = list.remove(hrl(info9, sn2));
+    assertNull(list.getRegionLocation(0));
+    assertEquals(sn1, list.getRegionLocation(1).getServerName());
+    assertEquals(sn2, list.getRegionLocation(2).getServerName());
+    assertNull(list.getRegionLocation(5));
+    assertNull(list.getRegionLocation(9));
+
+
+    // test multi-element remove from multi element list
+    list = hrll(hrl(info0, sn1), hrl(info1, sn1), hrl(info2, sn0), hrl(info9, sn0));
+    list = list.remove(hrl(info9, sn0));
+    assertEquals(sn1, list.getRegionLocation(0).getServerName());
+    assertEquals(sn1, list.getRegionLocation(1).getServerName());
+    assertEquals(sn0, list.getRegionLocation(2).getServerName());
+    assertNull(list.getRegionLocation(5));
+    assertNull(list.getRegionLocation(9));
+  }
+
+  @Test
+  public void testUpdateLocation() {
+    RegionLocations list;
+
+    // test add to empty list
+    list = new RegionLocations();
+    list = list.updateLocation(hrl(info0, sn1), false, false);
+    assertEquals(sn1, list.getRegionLocation(0).getServerName());
+
+    // test add to non-empty list
+    list = list.updateLocation(hrl(info9, sn3, 10), false, false);
+    assertEquals(sn3, list.getRegionLocation(9).getServerName());
+    assertEquals(10, list.size());
+    list = list.updateLocation(hrl(info2, sn2, 10), false, false);
+    assertEquals(sn2, list.getRegionLocation(2).getServerName());
+    assertEquals(10, list.size());
+
+    // test update greater SeqNum
+    list = list.updateLocation(hrl(info2, sn3, 11), false, false);
+    assertEquals(sn3, list.getRegionLocation(2).getServerName());
+    assertEquals(sn3, list.getRegionLocation(9).getServerName());
+
+    // test update equal SeqNum
+    list = list.updateLocation(hrl(info2, sn1, 11), false, false); // should not update
+    assertEquals(sn3, list.getRegionLocation(2).getServerName());
+    assertEquals(sn3, list.getRegionLocation(9).getServerName());
+    list = list.updateLocation(hrl(info2, sn1, 11), true, false); // should update
+    assertEquals(sn1, list.getRegionLocation(2).getServerName());
+    assertEquals(sn3, list.getRegionLocation(9).getServerName());
+
+    // test force update
+    list = list.updateLocation(hrl(info2, sn2, 9), false, true); // should update
+    assertEquals(sn2, list.getRegionLocation(2).getServerName());
+    assertEquals(sn3, list.getRegionLocation(9).getServerName());
+  }
+
+  @Test
+  public void testMergeLocations() {
+    RegionLocations list1, list2;
+
+    // test merge empty lists
+    list1 = new RegionLocations();
+    list2 = new RegionLocations();
+
+    assertTrue(list1 == list1.mergeLocations(list2));
+
+    // test merge non-empty and empty
+    list2 = hrll(hrl(info0, sn0));
+    list1 = list1.mergeLocations(list2);
+    assertEquals(sn0, list1.getRegionLocation(0).getServerName());
+
+    // test merge empty and non empty
+    list1 = hrll();
+    list1 = list2.mergeLocations(list1);
+    assertEquals(sn0, list1.getRegionLocation(0).getServerName());
+
+    // test merge non intersecting
+    list1 = hrll(hrl(info0, sn0), hrl(info1, sn1));
+    list2 = hrll(hrl(info2, sn2));
+    list1 = list2.mergeLocations(list1);
+    assertEquals(sn0, list1.getRegionLocation(0).getServerName());
+    assertEquals(sn1, list1.getRegionLocation(1).getServerName());
+    assertEquals(sn2, list1.getRegionLocation(2).getServerName());
+
+    // do the other way merge as well
+    list1 = hrll(hrl(info0, sn0), hrl(info1, sn1));
+    list2 = hrll(hrl(info2, sn2));
+    list1 = list1.mergeLocations(list2);
+    assertEquals(sn0, list1.getRegionLocation(0).getServerName());
+    assertEquals(sn1, list1.getRegionLocation(1).getServerName());
+    assertEquals(sn2, list1.getRegionLocation(2).getServerName());
+
+    // test intersecting lists same seqNum
+    list1 = hrll(hrl(info0, sn0), hrl(info1, sn1));
+    list2 = hrll(hrl(info0, sn2), hrl(info1, sn2), hrl(info9, sn3));
+    list1 = list2.mergeLocations(list1); // list1 should override
+    assertEquals(10, list1.size());
+    assertEquals(sn0, list1.getRegionLocation(0).getServerName());
+    assertEquals(sn1, list1.getRegionLocation(1).getServerName());
+    assertEquals(sn3, list1.getRegionLocation(9).getServerName());
+
+    // do the other way
+    list1 = hrll(hrl(info0, sn0), hrl(info1, sn1));
+    list2 = hrll(hrl(info0, sn2), hrl(info1, sn2), hrl(info9, sn3));
+    list1 = list1.mergeLocations(list2); // list2 should override
+    assertEquals(10, list1.size());
+    assertEquals(sn2, list1.getRegionLocation(0).getServerName());
+    assertEquals(sn2, list1.getRegionLocation(1).getServerName());
+    assertEquals(sn3, list1.getRegionLocation(9).getServerName());
+
+    // test intersecting lists different seqNum
+    list1 = hrll(hrl(info0, sn0, 10), hrl(info1, sn1, 10));
+    list2 = hrll(hrl(info0, sn2, 11), hrl(info1, sn2, 11), hrl(info9, sn3, 11));
+    list1 = list1.mergeLocations(list2); // list2 should override because of seqNum
+    assertEquals(10, list1.size());
+    assertEquals(sn2, list1.getRegionLocation(0).getServerName());
+    assertEquals(sn2, list1.getRegionLocation(1).getServerName());
+    assertEquals(sn3, list1.getRegionLocation(9).getServerName());
+
+    // do the other way
+    list1 = hrll(hrl(info0, sn0, 10), hrl(info1, sn1, 10));
+    list2 = hrll(hrl(info0, sn2, 11), hrl(info1, sn2, 11), hrl(info9, sn3, 11));
+    list1 = list1.mergeLocations(list2); // list2 should override
+    assertEquals(10, list1.size());
+    assertEquals(sn2, list1.getRegionLocation(0).getServerName());
+    assertEquals(sn2, list1.getRegionLocation(1).getServerName());
+    assertEquals(sn3, list1.getRegionLocation(9).getServerName());
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestClientNoCluster.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestClientNoCluster.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestClientNoCluster.java
index 2067e01..64301c1 100644
--- a/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestClientNoCluster.java
+++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/client/TestClientNoCluster.java
@@ -43,6 +43,7 @@ import org.apache.hadoop.hbase.HBaseConfiguration;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.RegionTooBusyException;
 import org.apache.hadoop.hbase.ServerName;
@@ -117,8 +118,9 @@ public class TestClientNoCluster extends Configured implements Tool {
     }
 
     @Override
-    public HRegionLocation getMetaRegionLocation() throws IOException {
-      return new HRegionLocation(HRegionInfo.FIRST_META_REGIONINFO, META_HOST);
+    public RegionLocations getMetaRegionLocation() throws IOException {
+      return new RegionLocations(
+        new HRegionLocation(HRegionInfo.FIRST_META_REGIONINFO, META_HOST));
     }
 
     @Override
@@ -142,7 +144,7 @@ public class TestClientNoCluster extends Configured implements Tool {
    * Remove the @Ignore to try out timeout and retry asettings
    * @throws IOException
    */
-  @Ignore 
+  @Ignore
   @Test
   public void testTimeoutAndRetries() throws IOException {
     Configuration localConfig = HBaseConfiguration.create(this.conf);
@@ -759,7 +761,7 @@ public class TestClientNoCluster extends Configured implements Tool {
     // an exception is thrown -- usually RegionTooBusyException when we have more than
     // hbase.test.multi.too.many requests outstanding at any time.
     getConf().setInt("hbase.client.start.log.errors.counter", 0);
- 
+
     // Ugly but this is only way to pass in configs.into ManyServersManyRegionsConnection class.
     getConf().setInt("hbase.test.regions", regions);
     getConf().setLong("hbase.test.namespace.span", namespaceSpan);

http://git-wip-us.apache.org/repos/asf/hbase/blob/d4b82224/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
----------------------------------------------------------------------
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
index b27679c..65f2086 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
@@ -397,13 +397,19 @@ public final class HConstants {
   public static final byte [] REGIONINFO_QUALIFIER = Bytes.toBytes(REGIONINFO_QUALIFIER_STR);
 
   /** The server column qualifier */
-  public static final byte [] SERVER_QUALIFIER = Bytes.toBytes("server");
+  public static final String SERVER_QUALIFIER_STR = "server";
+  /** The server column qualifier */
+  public static final byte [] SERVER_QUALIFIER = Bytes.toBytes(SERVER_QUALIFIER_STR);
 
   /** The startcode column qualifier */
-  public static final byte [] STARTCODE_QUALIFIER = Bytes.toBytes("serverstartcode");
+  public static final String STARTCODE_QUALIFIER_STR = "serverstartcode";
+  /** The startcode column qualifier */
+  public static final byte [] STARTCODE_QUALIFIER = Bytes.toBytes(STARTCODE_QUALIFIER_STR);
 
   /** The open seqnum column qualifier */
-  public static final byte [] SEQNUM_QUALIFIER = Bytes.toBytes("seqnumDuringOpen");
+  public static final String SEQNUM_QUALIFIER_STR = "seqnumDuringOpen";
+  /** The open seqnum column qualifier */
+  public static final byte [] SEQNUM_QUALIFIER = Bytes.toBytes(SEQNUM_QUALIFIER_STR);
 
   /** The state column qualifier */
   public static final byte [] STATE_QUALIFIER = Bytes.toBytes("state");