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 2014/08/01 00:07:46 UTC

[10/50] [abbrv] git commit: [HBASE-11303] New feature: expand cluster on specific hosts

[HBASE-11303] New feature: expand cluster on specific hosts

Summary:
we need to be able to expand a cluster onto specified hosts. Also this is a better solution then the expandRack, since the moving is done on per table basis.
- We first try to guess the new regionservers - but user can still specify them if we didn't guess correctly.
- Then we keep a priorityqueue of regionservers (sorted in descending order) by number of regions assigned.
- We take regions from these servers and move the tertiary to a new location
- RegionPlacement -u should be run in the end to complete the movement

Test Plan: will write a unit test

Reviewers: manukranthk, liyintang, aaiyer, fan, gauravm, rshroff, daviddeng

Reviewed By: daviddeng

Subscribers: elliott, hbase-eng@

Differential Revision: https://phabricator.fb.com/D1332359

Tasks: 3041849

git-svn-id: svn+ssh://tubbs/svnhive/hadoop/branches/titan/VENDOR.hbase/hbase-trunk@42718 e7acf4d4-3532-417f-9e73-7a9ae25a1f51


Project: http://git-wip-us.apache.org/repos/asf/hbase/repo
Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/91eea3f6
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/91eea3f6
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/91eea3f6

Branch: refs/heads/0.89-fb
Commit: 91eea3f69f6ff07e493b0acaf2a256c2122c1ecd
Parents: ce2595d
Author: adela <ad...@e7acf4d4-3532-417f-9e73-7a9ae25a1f51>
Authored: Fri Jun 6 17:08:07 2014 +0000
Committer: Elliott Clark <el...@fb.com>
Committed: Thu Jul 31 14:44:22 2014 -0700

----------------------------------------------------------------------
 .../hadoop/hbase/master/AssignmentDomain.java   |  31 ++-
 .../hbase/master/RegionAssignmentSnapshot.java  |  26 ++-
 .../hadoop/hbase/master/RegionPlacement.java    | 226 +++++++++++++++----
 .../hbase/master/RegionPlacementTestBase.java   |  35 ++-
 .../hbase/master/TestRegionPlacement.java       | 129 ++++++++++-
 5 files changed, 370 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hbase/blob/91eea3f6/src/main/java/org/apache/hadoop/hbase/master/AssignmentDomain.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/hadoop/hbase/master/AssignmentDomain.java b/src/main/java/org/apache/hadoop/hbase/master/AssignmentDomain.java
index 29e97ea..5d10a53 100644
--- a/src/main/java/org/apache/hadoop/hbase/master/AssignmentDomain.java
+++ b/src/main/java/org/apache/hadoop/hbase/master/AssignmentDomain.java
@@ -30,7 +30,6 @@ import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 
-import com.google.common.collect.Lists;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
@@ -38,6 +37,8 @@ import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HServerAddress;
 import org.apache.hadoop.hbase.HTableDescriptor;
 
+import com.google.common.collect.Lists;
+
 public class AssignmentDomain {
   protected static final Log LOG =
     LogFactory.getLog(AssignmentDomain.class.getClass());
@@ -231,4 +232,32 @@ public class AssignmentDomain {
       return false;
     return true;
   }
+
+  /**
+   * Given list of hostnames (Set of strings) you will get list of
+   * HServerAddress. The order will be perserved i.e if you passed in the set
+   * (h1, 2, 3) you will get (HServerAddres1, 2, 3)
+   *
+   * @param hostnames
+   *          - Set of String
+   * @return - List of corresponding HServerAddress
+   */
+  public List<HServerAddress> getHServerAddressFromHostname(
+      Set<String> hostnames) {
+    List<HServerAddress> serversToReturn = new ArrayList<HServerAddress>();
+    Set<HServerAddress> servers = regionServerToRackMap.keySet();
+    Map<String, HServerAddress> allServerNameToAddress = new HashMap<>();
+    for (HServerAddress address : servers) {
+      allServerNameToAddress.put(address.getHostname(), address);
+    }
+    for (String host : hostnames) {
+      HServerAddress hostAddress = allServerNameToAddress.get(host);
+      if (hostAddress == null) {
+        LOG.error("Host: " + hostAddress + " is not recognized!!!");
+      } else {
+        serversToReturn.add(hostAddress);
+      }
+    }
+    return serversToReturn;
+  }
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/91eea3f6/src/main/java/org/apache/hadoop/hbase/master/RegionAssignmentSnapshot.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/hadoop/hbase/master/RegionAssignmentSnapshot.java b/src/main/java/org/apache/hadoop/hbase/master/RegionAssignmentSnapshot.java
index 9e80713..75b9df1 100644
--- a/src/main/java/org/apache/hadoop/hbase/master/RegionAssignmentSnapshot.java
+++ b/src/main/java/org/apache/hadoop/hbase/master/RegionAssignmentSnapshot.java
@@ -54,6 +54,7 @@ public class RegionAssignmentSnapshot {
   /** the region name to region info map */
   private final Map<String, HRegionInfo> regionNameToRegionInfoMap;
   
+  private final Map<String, Map<HServerAddress, List<HRegionInfo>>> rsToRegionsPerTable;
   /** the regionServer to region map */
   private final Map<HServerAddress, List<HRegionInfo>> regionServerToRegionMap;
   /** the existing assignment plan in the META region */
@@ -67,6 +68,7 @@ public class RegionAssignmentSnapshot {
     regionToRegionServerMap = new HashMap<HRegionInfo, HServerAddress>();
     regionServerToRegionMap = new HashMap<HServerAddress, List<HRegionInfo>>();
     regionNameToRegionInfoMap = new TreeMap<String, HRegionInfo>();
+    rsToRegionsPerTable = new HashMap<>();
     exsitingAssignmentPlan = new AssignmentPlan();
     globalAssignmentDomain = new AssignmentDomain(conf);
   }
@@ -77,7 +79,7 @@ public class RegionAssignmentSnapshot {
    */
   public void initialize() throws IOException {
     LOG.info("Start to scan the META for the current region assignment " +
-    		"snappshot");
+        "snapshot");
     
     // Add all the online region servers
     HBaseAdmin admin  = new HBaseAdmin(conf);
@@ -160,6 +162,20 @@ public class RegionAssignmentSnapshot {
       }
       regionList.add(regionInfo);
       regionServerToRegionMap.put(server, regionList);
+
+      // update rsToRegionsPerTable accordingly
+      String tblName = regionInfo.getTableDesc().getNameAsString();
+      Map<HServerAddress, List<HRegionInfo>> assignment = rsToRegionsPerTable.get(tblName);
+      if (assignment== null){
+        assignment = new HashMap<>();
+        rsToRegionsPerTable.put(tblName, assignment);
+      }
+      List<HRegionInfo> regions = assignment.get(server);
+      if (regions == null) {
+        regions = new ArrayList<>();
+        assignment.put(server, regions);
+      }
+      regions.add(regionInfo);
     } 
   }
 
@@ -190,4 +206,12 @@ public class RegionAssignmentSnapshot {
   public Set<String> getTableSet() {
     return this.tableToRegionMap.keySet();
   }
+
+  /**
+   * Returns assignment regionserver->regions per table
+   *
+   */
+  public Map<String, Map<HServerAddress, List<HRegionInfo>>> getRegionServerToRegionsPerTable() {
+    return this.rsToRegionsPerTable;
+  }
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/91eea3f6/src/main/java/org/apache/hadoop/hbase/master/RegionPlacement.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/hadoop/hbase/master/RegionPlacement.java b/src/main/java/org/apache/hadoop/hbase/master/RegionPlacement.java
index b91c29e..6c7e7aa 100644
--- a/src/main/java/org/apache/hadoop/hbase/master/RegionPlacement.java
+++ b/src/main/java/org/apache/hadoop/hbase/master/RegionPlacement.java
@@ -31,6 +31,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.PriorityQueue;
 import java.util.Random;
 import java.util.Scanner;
 import java.util.Set;
@@ -640,6 +641,107 @@ public class RegionPlacement implements RegionPlacementPolicy{
   }
 
   /**
+   * Used to expand cluster by adding more hosts. You don't need to use this
+   * when you are adding just a few hosts, but when you add a bunch of them
+   * (let's say >5 at once)
+   *
+   * We are iterating through each table and we are moving one region at a time
+   * starting from the hosts which contain largest number of regions. Note that
+   * only the tertiary will be updated, you will still need to update the
+   * assignment plan after a few days after locality builds up.
+   *
+   * @param newHosts
+   *          - new hosts where we expand the cluster
+   * @param assignmentSnapshot
+   *          - current assignment snapshot
+   * @throws IOException
+   */
+  public AssignmentPlan expandRegionsToNewHosts(List<HServerAddress> newHosts,
+      RegionAssignmentSnapshot assignmentSnapshot) throws IOException {
+
+    AssignmentPlan plan = assignmentSnapshot.getExistingAssignmentPlan();
+
+    Set<String> allTables = assignmentSnapshot.getTableSet();
+    for (String t : allTables) {
+      int totalRegionsPerTable = 0;
+      PriorityQueue<Pair<HServerAddress, List<HRegionInfo>>> pqueue = new PriorityQueue<>(
+          200, new Comparator<Pair<HServerAddress, List<HRegionInfo>>>() {
+
+            @Override
+            public int compare(Pair<HServerAddress, List<HRegionInfo>> p1,
+                Pair<HServerAddress, List<HRegionInfo>> p2) {
+              return -Integer.compare(p1.getSecond().size(), p2.getSecond()
+                  .size());
+            }
+          });
+      Map<HServerAddress, List<HRegionInfo>> rsToRegionsPerTable = assignmentSnapshot
+          .getRegionServerToRegionsPerTable().get(t);
+      for (Entry<HServerAddress, List<HRegionInfo>> entry : rsToRegionsPerTable.entrySet()) {
+        System.out.println("server: " + entry.getKey() + " regions" + entry.getValue().size());
+        totalRegionsPerTable +=entry.getValue().size();
+      }
+      for (Entry<HServerAddress, List<HRegionInfo>> entry : rsToRegionsPerTable
+          .entrySet()) {
+        pqueue.add(new Pair<HServerAddress, List<HRegionInfo>>(entry.getKey(),
+            entry.getValue()));
+      }
+
+      // for each of the new machines - calculate how many regions of this table
+      // should be placed
+
+      // calculate overall avg of regions per host (including new hosts as well)
+      AssignmentDomain domain = assignmentSnapshot.getGlobalAssignmentDomain();
+      System.out.println("rs: " + domain.getAllServers().size());
+      double avgPerHosts = (double) totalRegionsPerTable
+          / domain.getAllServers().size();
+      int avgPerHostFloor = (int) Math.floor(avgPerHosts);
+      Random rand = new Random();
+      for (HServerAddress server : newHosts) {
+        int neededRegionsOnNewHost = avgPerHostFloor
+            + (rand.nextDouble() < (avgPerHosts - avgPerHostFloor) ? 1 : 0);
+        // now start taking regions from the existing hosts (they are sorted by
+        // number of regions in descending order)
+        int placedRegionOnNew = 0;
+        while (placedRegionOnNew < neededRegionsOnNewHost) {
+          Pair<HServerAddress, List<HRegionInfo>> onOld = pqueue.poll();
+          HRegionInfo regionToMove = onOld.getSecond().remove(0);
+          // now return back the modified pair in the priority queue
+          pqueue.add(onOld);
+          placedRegionOnNew++;
+          // move the tertiary of the region to the new server
+          List<HServerAddress> favoredNodes = plan.getAssignment(regionToMove);
+          AssignmentPlan.replaceFavoredNodesServerWithNew(favoredNodes,
+              AssignmentPlan.POSITION.TERTIARY, server);
+          plan.updateAssignmentPlan(regionToMove, favoredNodes);
+        }
+        System.out.println("Server: " + server + " will get "
+            + placedRegionOnNew + " tertiary assignments for table " + t);
+      }
+    }
+    return plan;
+  }
+
+  /**
+   * @param plan
+   * @throws IOException
+   */
+  private void userUpdatePlan(AssignmentPlan plan) throws IOException {
+    System.out
+        .println("Do you want to update the assignment plan with this changes (y/n): ");
+    Scanner s = new Scanner(System.in);
+    String input = s.nextLine().trim();
+    s.close();
+    if (input.toLowerCase().equals("y")) {
+      System.out.println("Updating assignment plan...");
+      updateAssignmentPlanToMeta(plan);
+      updateAssignmentPlanToRegionServers(plan);
+    } else {
+      System.out.println("exiting without updating the assignment plan");
+    }
+  }
+
+
+  /**
    * This method will pick regions from a given rack, such that these regions
    * are going to be moved to the new rack later. The logic behind is: we move
    * the regions' tertiaries into a new rack
@@ -702,12 +804,14 @@ public class RegionPlacement implements RegionPlacementPolicy{
       serverIndex++;
     }
     System.out.println("Total number of regions: " + totalMovedPerRack
-        + " in rack " + currentRack + " will move its tertiary to a new rack: "
+        + " in rack " + currentRack + " will move its tertiary to the new : "
         + newRack);
     System.out.println("------------------------------------------");
     return regionsToMove;
   }
 
+
+
   /**
    * Returns the average number of regions per regionserver
    *
@@ -730,11 +834,15 @@ public class RegionPlacement implements RegionPlacementPolicy{
    * Move the regions to the new rack, such that each server will get equal
    * number of regions
    *
-   * @param plan - the current assignment plan
-   * @param domain - the assignment domain
-   * @param regionsToMove - the regions that would be moved to the new rack
-   * regionserver per rack are picked to be moved in the new rack
-   * @param newRack - the new rack
+   * @param plan
+   *          - the current assignment plan
+   * @param domain
+   *          - the assignment domain
+   * @param regionsToMove
+   *          - the regions that would be moved to the new rack regionserver per
+   *          rack are picked to be moved in the new rack
+   * @param newRack
+   *          - the new rack
    * @throws IOException
    */
   private void moveRegionsToNewRack(AssignmentPlan plan,
@@ -742,7 +850,8 @@ public class RegionPlacement implements RegionPlacementPolicy{
       throws IOException {
     System.out.println("------------------------------------------");
     System.out
-        .println("Printing how many regions are planned to be assigned per region server in the new rack (" + newRack + ")");
+        .println("Printing how many regions are planned to be assigned per region server in the new rack ("
+            + newRack + ")");
     List<HServerAddress> serversFromNewRack = domain
         .getServersFromRack(newRack);
     int totalNumRSNewRack = serversFromNewRack.size();
@@ -761,19 +870,9 @@ public class RegionPlacement implements RegionPlacementPolicy{
       System.out.println("RS: " + serversFromNewRack.get(j).getHostname()
           + " got " + regionsPerRs + "tertiary regions");
     }
-    System.out
-        .println("Do you want to update the assignment plan with this changes (y/n): ");
-    Scanner s = new Scanner(System.in);
-    String input = s.nextLine().trim();
-    s.close();
-    if (input.toLowerCase().equals("y")) {
-      System.out.println("Updating assignment plan...");
-      updateAssignmentPlanToMeta(plan);
-      updateAssignmentPlanToRegionServers(plan);
-    } else {
-      System.out.println("exiting without updating the assignment plan");
-    }
+    userUpdatePlan(plan);
   }
+
   /**
    * Generate the assignment plan for the existing table
    *
@@ -1657,6 +1756,7 @@ public class RegionPlacement implements RegionPlacementPolicy{
         "use munkres to place secondaries and tertiaries");
     opt.addOption("ld", "locality-dispersion", false, "print locality and dispersion information for current plan");
     opt.addOption("exprack", "expand-with-rack", false, "expand the regions to a new rack");
+    opt.addOption("expHosts", false, "expand the regions to a new rack");
     opt.addOption("rnum", false, "print number of primaries per RS");
     opt.addOption("bp", "balance-primary", false, "balance the primaries across all regionservers in the cluster");
     try {
@@ -1733,14 +1833,7 @@ public class RegionPlacement implements RegionPlacementPolicy{
             .getRegionDegreeLocalityMappingFromFS(conf);
         Map<String, Integer> movesPerTable = rp.getRegionsMovement(newPlan);
         rp.checkDifferencesWithOldPlan(movesPerTable, locality, newPlan);
-        System.out.println("Do you want to update the assignment plan? [y/n]");
-        Scanner s = new Scanner(System.in);
-        String input = s.nextLine().trim();
-        if (input.equals("y")) {
-          System.out.println("Updating assignment plan...");
-          rp.updateAssignmentPlan(newPlan);
-        }
-        s.close();
+        rp.userUpdatePlan(newPlan);
       }
       // Read all the modes
       else if (cmd.hasOption("v") || cmd.hasOption("verify")) {
@@ -1773,14 +1866,7 @@ public class RegionPlacement implements RegionPlacementPolicy{
         System.out.println("Printing how will distribution of primaries look like");
         rp.printDistributionOfPrimariesPerTable(rp.getRegionAssignmentSnapshot(), newPlan);
         rp.printDistributionOfPrimariesPerCell(rp.getExistingAssignmentPlan(), newPlan);
-        System.out.println("Do you want to update the assignment plan? [y/n]");
-        Scanner s = new Scanner(System.in);
-        String input = s.nextLine().trim();
-        if (input.equals("y")) {
-          System.out.println("Updating assignment plan...");
-          rp.updateAssignmentPlan(newPlan);
-        }
-        s.close();
+        rp.userUpdatePlan(newPlan);
       } else if (cmd.hasOption("ld")) {
         Map<String, Map<String, Float>> locality = FSUtils
             .getRegionDegreeLocalityMappingFromFS(conf);
@@ -1831,6 +1917,8 @@ public class RegionPlacement implements RegionPlacementPolicy{
         String newRack = s.nextLine().trim();
         s.close();
         rp.expandRegionsToNewRack(newRack, snapshot);
+      } else if (cmd.hasOption("expHosts")) {
+        expandClusterWithNewHosts(rp);
       } else if (cmd.hasOption("upload")) {
         String fileName = cmd.getOptionValue("upload");
         try {
@@ -1840,14 +1928,7 @@ public class RegionPlacement implements RegionPlacementPolicy{
               .getRegionDegreeLocalityMappingFromFS(conf);
           Map<String, Integer> movesPerTable = rp.getRegionsMovement(newPlan);
           rp.checkDifferencesWithOldPlan(movesPerTable, locality, newPlan);
-          System.out.println("Do you want to update the assignment plan? [y/n]");
-          Scanner s = new Scanner(System.in);
-          String input = s.nextLine().trim();
-          if (input.equals("y")) {
-            System.out.println("Updating assignment plan...");
-            rp.updateAssignmentPlan(newPlan);
-          }
-          s.close();
+          rp.userUpdatePlan(newPlan);
         } catch (JsonParseException je) {
           LOG.error("Unable to parse json file", je);
         } catch (IOException e) {
@@ -1874,6 +1955,65 @@ public class RegionPlacement implements RegionPlacementPolicy{
     }
   }
 
+  /**
+   * Entry-point when expanding a cluster with a number of hosts
+   *
+   * @param rp
+   *          - regionPlacement object
+   * @throws IOException
+   */
+  private static void expandClusterWithNewHosts(RegionPlacement rp)
+      throws IOException {
+    RegionAssignmentSnapshot snapshot = rp.getRegionAssignmentSnapshot();
+    System.out.println("List of all hosts with #regions assigned: ");
+    Map<HServerAddress, List<HRegionInfo>> serverToRegions = snapshot
+        .getRegionServerToRegionMap();
+    // these are potential hosts - to which we want to expand
+    List<HServerAddress> emptyHosts = new ArrayList<>();
+    for (Entry<HServerAddress, List<HRegionInfo>> entry : serverToRegions
+        .entrySet()) {
+      System.out.println(entry.getKey().getHostname() + "\t:\t"
+          + entry.getValue().size());
+      if (entry.getValue().size() == 0) {
+        emptyHosts.add(entry.getKey());
+      }
+    }
+    System.out
+        .println("Guessing with which hosts you want to expand the cluster");
+    StringBuilder sb = new StringBuilder();
+    for (HServerAddress server : emptyHosts) {
+      sb.append(server.getHostname());
+      sb.append(",");
+    }
+    // remove the last comma
+    sb.deleteCharAt(sb.length() - 1);
+    System.out.println("If all the hosts are correct just press Y, otherwise "
+        + "specify the list of the hosts in one line (comma separated)");
+    System.out.println("Example:");
+    System.out.println();
+    System.out.println("hbase123.xyz, hbase456.xyz, hbase789.xyz");
+    Scanner s = new Scanner(System.in);
+    String answer = s.nextLine().trim();
+    s.close();
+    List<HServerAddress> specifiedHosts = null;
+    if (!answer.equalsIgnoreCase("Y")) {
+      Set<String> specifiedStrings = new HashSet<String>();
+      String[] hosts = answer.split(",");
+      for (String host : hosts) {
+        specifiedStrings.add(host.trim());
+      }
+      specifiedHosts = snapshot.getGlobalAssignmentDomain()
+          .getHServerAddressFromHostname(specifiedStrings);
+      if (specifiedHosts == null) {
+        System.out.println("One or more of the specified hosts were not recognized, ABORTING");
+        return;
+      }
+    }
+    AssignmentPlan newPlan = rp.expandRegionsToNewHosts(
+        specifiedHosts == null ? emptyHosts : specifiedHosts, snapshot);
+    rp.userUpdatePlan(newPlan);
+  }
+
   private static void printHelp(Options opt) {
     new HelpFormatter().printHelp(
         "RegionPlacement < -w | -u | -n | -v | -t | -h | -overwrite -r regionName -f favoredNodes " +

http://git-wip-us.apache.org/repos/asf/hbase/blob/91eea3f6/src/test/java/org/apache/hadoop/hbase/master/RegionPlacementTestBase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/hadoop/hbase/master/RegionPlacementTestBase.java b/src/test/java/org/apache/hadoop/hbase/master/RegionPlacementTestBase.java
index 873a33f..7ef600d 100644
--- a/src/test/java/org/apache/hadoop/hbase/master/RegionPlacementTestBase.java
+++ b/src/test/java/org/apache/hadoop/hbase/master/RegionPlacementTestBase.java
@@ -19,41 +19,34 @@
  */
 package org.apache.hadoop.hbase.master;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HColumnDescriptor;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HServerAddress;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.client.HBaseAdmin;
-import org.apache.hadoop.hbase.client.HTable;
-import org.apache.hadoop.hbase.client.Put;
 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.regionserver.HRegion;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
-import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Writables;
 
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
 public class RegionPlacementTestBase {
   protected final static RegionMovementTestHelper TEST_UTIL = new RegionMovementTestHelper();
   protected final static int META_REGION_OVERHEAD = 1;

http://git-wip-us.apache.org/repos/asf/hbase/blob/91eea3f6/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement.java b/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement.java
index 60cea48..83ac5bf 100644
--- a/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement.java
+++ b/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement.java
@@ -19,7 +19,21 @@
  */
 package org.apache.hadoop.hbase.master;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
 import org.apache.hadoop.hbase.HColumnDescriptor;
+import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HServerAddress;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.LargeTests;
@@ -32,21 +46,11 @@ import org.apache.hadoop.hbase.util.Bytes;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 @Category(LargeTests.class)
 public class TestRegionPlacement extends RegionPlacementTestBase {
 
@@ -339,5 +343,108 @@ public class TestRegionPlacement extends RegionPlacementTestBase {
     assertEquals("Loaded plan should be the same with current plan", currentPlan, loadedPlan);
 
   }
+
+  /**
+   * First create a table on pinned servers. Then change the table descriptor
+   * such that the table is not pinned anymore and can be assigned on all
+   * machines in the cluster. Then we call expandOnHosts passing the empty
+   * regionserver and in the end we verify that he received avg number of
+   * tertiaries
+   *
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  @Test
+  public void testExpandOnHosts() throws IOException, InterruptedException {
+    String tableName = "testExpandHosts";
+    // let's have more region for this test case
+    REGION_NUM = 20;
+
+    TEST_UTIL.resetLastOpenedRegionCount();
+    resetLastRegionOnPrimary();
+
+    try {
+      MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
+      HTableDescriptor htd = new HTableDescriptor(tableName);
+      htd.addFamily(new HColumnDescriptor("d"));
+
+      assertTrue("number of slaves is smaller then 3", SLAVES >= 3);
+      Set<HServerAddress> servers = new HashSet<>(3);
+      HRegionServer unusedServer = cluster.getRegionServer(3);
+
+      for (int i = 0; i < 3; i++) {
+        servers.add(cluster.getRegionServer(i).getServerInfo()
+            .getServerAddress());
+      }
+
+      htd.setServers(servers);
+      admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
+      admin.createTable(htd, Bytes.toBytes("aaaa"), Bytes.toBytes("zzzz"),
+          REGION_NUM);
+
+      // Wait for things to stabilize
+      TEST_UTIL.waitOnTable(tableName);
+      TEST_UTIL.waitOnStableRegionMovement();
+
+      // Reset all of the counters.
+      resetLastRegionOnPrimary();
+      TEST_UTIL.resetLastOpenedRegionCount();
+
+      verifyRegionAssignment(rp.getExistingAssignmentPlan(), 0, REGION_NUM);
+      assertPinned(tableName, cluster, servers, unusedServer);
+
+      // change the table descriptor now
+      htd.setServers(null);
+      admin.disableTable(tableName);
+      admin.modifyTable(Bytes.toBytes(tableName), htd);
+      admin.enableTable(tableName);
+
+      // Wait for things to stabilize
+      TEST_UTIL.waitOnTable(tableName);
+      TEST_UTIL.waitOnStableRegionMovement();
+
+      // Reset all of the counters.
+      resetLastRegionOnPrimary();
+      TEST_UTIL.resetLastOpenedRegionCount();
+
+      //verify there are no primaries on the last hosts from the existing table
+      HServerAddress newHost = cluster.getRegionServer(3).getServerInfo()
+          .getServerAddress();
+      Map<HRegionInfo, List<HServerAddress>> currentMap = rp.getExistingAssignmentPlan().getAssignmentMap();
+      for (List<HServerAddress> val : currentMap.values()) {
+        if (val.contains(newHost)) {
+          Assert.fail("new regionserver is contained in existing assignment");
+        }
+      }
+
+      List<HServerAddress> newHosts = new ArrayList<>();
+      newHosts.add(newHost);
+      AssignmentPlan newPlan = rp.expandRegionsToNewHosts(newHosts, rp.getRegionAssignmentSnapshot());
+      rp.updateAssignmentPlan(newPlan);
+
+      // verify there are tertiaries on the new host
+      Map<HRegionInfo, List<HServerAddress>> assignMap = newPlan.getAssignmentMap();
+      int regionsOnNewHost = 0;
+      for (List<HServerAddress> serversFromAssignment : assignMap.values()) {
+        //verify it is the tertiary
+        if (serversFromAssignment.get(AssignmentPlan.POSITION.TERTIARY.ordinal()).equals(newHost)) {
+          regionsOnNewHost++;
+        }
+      }
+      // we expect an average number of regions to move to the new host
+      assertEquals("avg number of regions to the new host: ", (int) REGION_NUM
+          / SLAVES, regionsOnNewHost);
+    } catch (Exception e) {
+      Assert.fail(e.getMessage());
+    } finally {
+      if (admin != null) {
+        admin.disableTable(tableName);
+        admin.deleteTable(tableName);
+        admin.close();
+        admin = null;
+      }
+    }
+  }
+
 }