001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.rsgroup;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.commons.lang3.StringUtils;
032import org.apache.hadoop.hbase.NamespaceDescriptor;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.client.RegionInfo;
036import org.apache.hadoop.hbase.constraint.ConstraintException;
037import org.apache.hadoop.hbase.master.HMaster;
038import org.apache.hadoop.hbase.master.LoadBalancer;
039import org.apache.hadoop.hbase.master.MasterServices;
040import org.apache.hadoop.hbase.master.RegionPlan;
041import org.apache.hadoop.hbase.master.RegionState;
042import org.apache.hadoop.hbase.master.ServerManager;
043import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
044import org.apache.hadoop.hbase.master.assignment.RegionStates.RegionStateNode;
045import org.apache.hadoop.hbase.net.Address;
046import org.apache.yetus.audience.InterfaceAudience;
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049
050import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
051import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
052
053/**
054 * Service to support Region Server Grouping (HBase-6721).
055 */
056@InterfaceAudience.Private
057public class RSGroupAdminServer implements RSGroupAdmin {
058  private static final Logger LOG = LoggerFactory.getLogger(RSGroupAdminServer.class);
059  public static final String KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE = "should keep at least " +
060          "one server in 'default' RSGroup.";
061
062  private MasterServices master;
063  private final RSGroupInfoManager rsGroupInfoManager;
064
065  public RSGroupAdminServer(MasterServices master, RSGroupInfoManager rsGroupInfoManager)
066      throws IOException {
067    this.master = master;
068    this.rsGroupInfoManager = rsGroupInfoManager;
069  }
070
071  @Override
072  public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
073    return rsGroupInfoManager.getRSGroup(groupName);
074  }
075
076  @Override
077  public RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException {
078    // We are reading across two Maps in the below with out synchronizing across
079    // them; should be safe most of the time.
080    String groupName = rsGroupInfoManager.getRSGroupOfTable(tableName);
081    return groupName == null? null: rsGroupInfoManager.getRSGroup(groupName);
082  }
083
084  private void checkOnlineServersOnly(Set<Address> servers) throws ConstraintException {
085    // This uglyness is because we only have Address, not ServerName.
086    // Online servers are keyed by ServerName.
087    Set<Address> onlineServers = new HashSet<>();
088    for(ServerName server: master.getServerManager().getOnlineServers().keySet()) {
089      onlineServers.add(server.getAddress());
090    }
091    for (Address address: servers) {
092      if (!onlineServers.contains(address)) {
093        throw new ConstraintException(
094            "Server " + address + " is not an online server in 'default' RSGroup.");
095      }
096    }
097  }
098
099  /**
100   * Check passed name. Fail if nulls or if corresponding RSGroupInfo not found.
101   * @return The RSGroupInfo named <code>name</code>
102   */
103  private RSGroupInfo getAndCheckRSGroupInfo(String name)
104  throws IOException {
105    if (StringUtils.isEmpty(name)) {
106      throw new ConstraintException("RSGroup cannot be null.");
107    }
108    RSGroupInfo rsGroupInfo = getRSGroupInfo(name);
109    if (rsGroupInfo == null) {
110      throw new ConstraintException("RSGroup does not exist: " + name);
111    }
112    return rsGroupInfo;
113  }
114
115  /**
116   * @return List of Regions associated with this <code>server</code>.
117   */
118  private List<RegionInfo> getRegions(final Address server) {
119    LinkedList<RegionInfo> regions = new LinkedList<>();
120    for (Map.Entry<RegionInfo, ServerName> el :
121        master.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()) {
122      if (el.getValue() == null) {
123        continue;
124      }
125
126      if (el.getValue().getAddress().equals(server)) {
127        addRegion(regions, el.getKey());
128      }
129    }
130    for (RegionStateNode state : master.getAssignmentManager().getRegionsInTransition()) {
131      if (state.getRegionLocation().getAddress().equals(server)) {
132        addRegion(regions, state.getRegionInfo());
133      }
134    }
135    return regions;
136  }
137
138  private void addRegion(final LinkedList<RegionInfo> regions, RegionInfo hri) {
139    // If meta, move it last otherwise other unassigns fail because meta is not
140    // online for them to update state in. This is dodgy. Needs to be made more
141    // robust. See TODO below.
142    if (hri.isMetaRegion()) {
143      regions.addLast(hri);
144    } else {
145      regions.addFirst(hri);
146    }
147  }
148
149  /**
150   * Check servers and tables.
151   *
152   * @param servers servers to move
153   * @param tables tables to move
154   * @param targetGroupName target group name
155   * @throws IOException if nulls or if servers and tables not belong to the same group
156   */
157  private void checkServersAndTables(Set<Address> servers, Set<TableName> tables,
158                                     String targetGroupName) throws IOException {
159    // Presume first server's source group. Later ensure all servers are from this group.
160    Address firstServer = servers.iterator().next();
161    RSGroupInfo tmpSrcGrp = rsGroupInfoManager.getRSGroupOfServer(firstServer);
162    if (tmpSrcGrp == null) {
163      // Be careful. This exception message is tested for in TestRSGroupsBase...
164      throw new ConstraintException("Source RSGroup for server " + firstServer
165              + " does not exist.");
166    }
167    RSGroupInfo srcGrp = new RSGroupInfo(tmpSrcGrp);
168    if (srcGrp.getName().equals(targetGroupName)) {
169      throw new ConstraintException("Target RSGroup " + targetGroupName +
170              " is same as source " + srcGrp.getName() + " RSGroup.");
171    }
172    // Only move online servers
173    checkOnlineServersOnly(servers);
174
175    // Ensure all servers are of same rsgroup.
176    for (Address server: servers) {
177      String tmpGroup = rsGroupInfoManager.getRSGroupOfServer(server).getName();
178      if (!tmpGroup.equals(srcGrp.getName())) {
179        throw new ConstraintException("Move server request should only come from one source " +
180                "RSGroup. Expecting only " + srcGrp.getName() + " but contains " + tmpGroup);
181      }
182    }
183
184    // Ensure all tables and servers are of same rsgroup.
185    for (TableName table : tables) {
186      String tmpGroup = rsGroupInfoManager.getRSGroupOfTable(table);
187      if (!tmpGroup.equals(srcGrp.getName())) {
188        throw new ConstraintException("Move table request should only come from one source " +
189                "RSGroup. Expecting only " + srcGrp.getName() + " but contains " + tmpGroup);
190      }
191    }
192
193    if (srcGrp.getServers().size() <= servers.size() && srcGrp.getTables().size() > tables.size()) {
194      throw new ConstraintException("Cannot leave a RSGroup " + srcGrp.getName() +
195              " that contains tables without servers to host them.");
196    }
197  }
198
199  /**
200   * Moves every region from servers which are currently located on these servers,
201   * but should not be located there.
202   * @param servers the servers that will move to new group
203   * @param tables these tables will be kept on the servers, others will be moved
204   * @param targetGroupName the target group name
205   * @throws IOException if moving the server and tables fail
206   */
207  private void moveRegionsFromServers(Set<Address> servers, Set<TableName> tables,
208      String targetGroupName) throws IOException {
209    boolean foundRegionsToMove;
210    RSGroupInfo targetGrp = getRSGroupInfo(targetGroupName);
211    Set<Address> allSevers = new HashSet<>(servers);
212    do {
213      foundRegionsToMove = false;
214      for (Iterator<Address> iter = allSevers.iterator(); iter.hasNext();) {
215        Address rs = iter.next();
216        // Get regions that are associated with this server and filter regions by tables.
217        List<RegionInfo> regions = new ArrayList<>();
218        for (RegionInfo region : getRegions(rs)) {
219          if (!tables.contains(region.getTable())) {
220            regions.add(region);
221          }
222        }
223
224        LOG.info("Moving " + regions.size() + " region(s) from " + rs +
225            " for server move to " + targetGroupName);
226        if (!regions.isEmpty()) {
227          for (RegionInfo region: regions) {
228            // Regions might get assigned from tables of target group so we need to filter
229            if (!targetGrp.containsTable(region.getTable())) {
230              this.master.getAssignmentManager().move(region);
231              if (master.getAssignmentManager().getRegionStates().
232                  getRegionState(region).isFailedOpen()) {
233                continue;
234              }
235              foundRegionsToMove = true;
236            }
237          }
238        }
239        if (!foundRegionsToMove) {
240          iter.remove();
241        }
242      }
243      try {
244        rsGroupInfoManager.wait(1000);
245      } catch (InterruptedException e) {
246        LOG.warn("Sleep interrupted", e);
247        Thread.currentThread().interrupt();
248      }
249    } while (foundRegionsToMove);
250  }
251
252  /**
253   * Moves every region of tables which should be kept on the servers,
254   * but currently they are located on other servers.
255   * @param servers the regions of these servers will be kept on the servers, others will be moved
256   * @param tables the tables that will move to new group
257   * @param targetGroupName the target group name
258   * @throws IOException if moving the region fails
259   */
260  private void moveRegionsToServers(Set<Address> servers, Set<TableName> tables,
261      String targetGroupName) throws IOException {
262    for (TableName table: tables) {
263      LOG.info("Moving region(s) from " + table + " for table move to " + targetGroupName);
264      for (RegionInfo region : master.getAssignmentManager().getRegionStates()
265          .getRegionsOfTable(table)) {
266        ServerName sn = master.getAssignmentManager().getRegionStates()
267            .getRegionServerOfRegion(region);
268        if (!servers.contains(sn.getAddress())) {
269          master.getAssignmentManager().move(region);
270        }
271      }
272    }
273  }
274
275  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
276      value="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE",
277      justification="Ignoring complaint because don't know what it is complaining about")
278  @Override
279  public void moveServers(Set<Address> servers, String targetGroupName)
280  throws IOException {
281    if (servers == null) {
282      throw new ConstraintException("The list of servers to move cannot be null.");
283    }
284    if (servers.isEmpty()) {
285      // For some reason this difference between null servers and isEmpty is important distinction.
286      // TODO. Why? Stuff breaks if I equate them.
287      return;
288    }
289    RSGroupInfo targetGrp = getAndCheckRSGroupInfo(targetGroupName);
290
291    // Hold a lock on the manager instance while moving servers to prevent
292    // another writer changing our state while we are working.
293    synchronized (rsGroupInfoManager) {
294      // Presume first server's source group. Later ensure all servers are from this group.
295      Address firstServer = servers.iterator().next();
296      RSGroupInfo srcGrp = rsGroupInfoManager.getRSGroupOfServer(firstServer);
297      if (srcGrp == null) {
298        // Be careful. This exception message is tested for in TestRSGroupsBase...
299        throw new ConstraintException("Source RSGroup for server " + firstServer
300            + " does not exist.");
301      }
302      if (srcGrp.getName().equals(targetGroupName)) {
303        throw new ConstraintException("Target RSGroup " + targetGroupName +
304            " is same as source " + srcGrp + " RSGroup.");
305      }
306      // Only move online servers (when moving from 'default') or servers from other
307      // groups. This prevents bogus servers from entering groups
308      if (RSGroupInfo.DEFAULT_GROUP.equals(srcGrp.getName())) {
309        if (srcGrp.getServers().size() <= servers.size()) {
310          throw new ConstraintException(KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE);
311        }
312        checkOnlineServersOnly(servers);
313      }
314      // Ensure all servers are of same rsgroup.
315      for (Address server: servers) {
316        String tmpGroup = rsGroupInfoManager.getRSGroupOfServer(server).getName();
317        if (!tmpGroup.equals(srcGrp.getName())) {
318          throw new ConstraintException("Move server request should only come from one source " +
319              "RSGroup. Expecting only " + srcGrp.getName() + " but contains " + tmpGroup);
320        }
321      }
322      if (srcGrp.getServers().size() <= servers.size() && srcGrp.getTables().size() > 0) {
323        throw new ConstraintException("Cannot leave a RSGroup " + srcGrp.getName() +
324            " that contains tables without servers to host them.");
325      }
326
327      // MovedServers may be < passed in 'servers'.
328      Set<Address> movedServers = rsGroupInfoManager.moveServers(servers, srcGrp.getName(),
329          targetGroupName);
330      List<Address> editableMovedServers = Lists.newArrayList(movedServers);
331      boolean foundRegionsToMove;
332      do {
333        foundRegionsToMove = false;
334        for (Iterator<Address> iter = editableMovedServers.iterator(); iter.hasNext();) {
335          Address rs = iter.next();
336          // Get regions that are associated with this server.
337          List<RegionInfo> regions = getRegions(rs);
338
339          LOG.info("Moving " + regions.size() + " region(s) from " + rs +
340              " for server move to " + targetGroupName);
341
342          for (RegionInfo region: regions) {
343            // Regions might get assigned from tables of target group so we need to filter
344            if (targetGrp.containsTable(region.getTable())) {
345              continue;
346            }
347            LOG.info("Moving region " + region.getShortNameToLog());
348            this.master.getAssignmentManager().move(region);
349            if (master.getAssignmentManager().getRegionStates().
350                getRegionState(region).isFailedOpen()) {
351              // If region is in FAILED_OPEN state, it won't recover, not without
352              // operator intervention... in hbase-2.0.0 at least. Continue rather
353              // than mark region as 'foundRegionsToMove'.
354              continue;
355            }
356            foundRegionsToMove = true;
357          }
358          if (!foundRegionsToMove) {
359            iter.remove();
360          }
361        }
362        try {
363          rsGroupInfoManager.wait(1000);
364        } catch (InterruptedException e) {
365          LOG.warn("Sleep interrupted", e);
366          Thread.currentThread().interrupt();
367        }
368      } while (foundRegionsToMove);
369
370      LOG.info("Move server done: " + srcGrp.getName() + "=>" + targetGroupName);
371    }
372  }
373
374  @Override
375  public void moveTables(Set<TableName> tables, String targetGroup) throws IOException {
376    if (tables == null) {
377      throw new ConstraintException("The list of servers cannot be null.");
378    }
379    if (tables.size() < 1) {
380      LOG.debug("moveTables() passed an empty set. Ignoring.");
381      return;
382    }
383
384    // Hold a lock on the manager instance while moving servers to prevent
385    // another writer changing our state while we are working.
386    synchronized (rsGroupInfoManager) {
387      if(targetGroup != null) {
388        RSGroupInfo destGroup = rsGroupInfoManager.getRSGroup(targetGroup);
389        if(destGroup == null) {
390          throw new ConstraintException("Target " + targetGroup + " RSGroup does not exist.");
391        }
392        if(destGroup.getServers().size() < 1) {
393          throw new ConstraintException("Target RSGroup must have at least one server.");
394        }
395      }
396
397      for (TableName table : tables) {
398        String srcGroup = rsGroupInfoManager.getRSGroupOfTable(table);
399        if(srcGroup != null && srcGroup.equals(targetGroup)) {
400          throw new ConstraintException(
401              "Source RSGroup " + srcGroup + " is same as target " + targetGroup +
402              " RSGroup for table " + table);
403        }
404        LOG.info("Moving table " + table.getNameAsString() + " to RSGroup " + targetGroup);
405      }
406      rsGroupInfoManager.moveTables(tables, targetGroup);
407
408      // targetGroup is null when a table is being deleted. In this case no further
409      // action is required.
410      if (targetGroup != null) {
411        for (TableName table: tables) {
412          if (master.getAssignmentManager().isTableDisabled(table)) {
413            LOG.debug("Skipping move regions because the table" + table + " is disabled.");
414            continue;
415          }
416          for (RegionInfo region :
417              master.getAssignmentManager().getRegionStates().getRegionsOfTable(table)) {
418            LOG.info("Moving region " + region.getShortNameToLog() +
419                " to RSGroup " + targetGroup);
420            master.getAssignmentManager().move(region);
421          }
422        }
423      }
424    }
425  }
426
427  @Override
428  public void addRSGroup(String name) throws IOException {
429    rsGroupInfoManager.addRSGroup(new RSGroupInfo(name));
430  }
431
432  @Override
433  public void removeRSGroup(String name) throws IOException {
434    // Hold a lock on the manager instance while moving servers to prevent
435    // another writer changing our state while we are working.
436    synchronized (rsGroupInfoManager) {
437      RSGroupInfo rsGroupInfo = rsGroupInfoManager.getRSGroup(name);
438      if (rsGroupInfo == null) {
439        throw new ConstraintException("RSGroup " + name + " does not exist");
440      }
441      int tableCount = rsGroupInfo.getTables().size();
442      if (tableCount > 0) {
443        throw new ConstraintException("RSGroup " + name + " has " + tableCount +
444            " tables; you must remove these tables from the rsgroup before " +
445            "the rsgroup can be removed.");
446      }
447      int serverCount = rsGroupInfo.getServers().size();
448      if (serverCount > 0) {
449        throw new ConstraintException("RSGroup " + name + " has " + serverCount +
450            " servers; you must remove these servers from the RSGroup before" +
451            "the RSGroup can be removed.");
452      }
453      for (NamespaceDescriptor ns: master.getClusterSchema().getNamespaces()) {
454        String nsGroup = ns.getConfigurationValue(rsGroupInfo.NAMESPACE_DESC_PROP_GROUP);
455        if (nsGroup != null &&  nsGroup.equals(name)) {
456          throw new ConstraintException("RSGroup " + name + " is referenced by namespace: " +
457              ns.getName());
458        }
459      }
460      rsGroupInfoManager.removeRSGroup(name);
461    }
462  }
463
464  @Override
465  public boolean balanceRSGroup(String groupName) throws IOException {
466    ServerManager serverManager = master.getServerManager();
467    AssignmentManager assignmentManager = master.getAssignmentManager();
468    LoadBalancer balancer = master.getLoadBalancer();
469
470    synchronized (balancer) {
471      // If balance not true, don't run balancer.
472      if (!((HMaster) master).isBalancerOn()) {
473        return false;
474      }
475
476      if (getRSGroupInfo(groupName) == null) {
477        throw new ConstraintException("RSGroup does not exist: "+groupName);
478      }
479      // Only allow one balance run at at time.
480      Map<String, RegionState> groupRIT = rsGroupGetRegionsInTransition(groupName);
481      if (groupRIT.size() > 0) {
482        LOG.debug("Not running balancer because " + groupRIT.size() + " region(s) in transition: " +
483          StringUtils.abbreviate(
484              master.getAssignmentManager().getRegionStates().getRegionsInTransition().toString(),
485              256));
486        return false;
487      }
488      if (serverManager.areDeadServersInProgress()) {
489        LOG.debug("Not running balancer because processing dead regionserver(s): " +
490            serverManager.getDeadServers());
491        return false;
492      }
493
494      //We balance per group instead of per table
495      List<RegionPlan> plans = new ArrayList<>();
496      for(Map.Entry<TableName, Map<ServerName, List<RegionInfo>>> tableMap:
497          getRSGroupAssignmentsByTable(groupName).entrySet()) {
498        LOG.info("Creating partial plan for table " + tableMap.getKey() + ": "
499            + tableMap.getValue());
500        List<RegionPlan> partialPlans = balancer.balanceCluster(tableMap.getValue());
501        LOG.info("Partial plan for table " + tableMap.getKey() + ": " + partialPlans);
502        if (partialPlans != null) {
503          plans.addAll(partialPlans);
504        }
505      }
506      long startTime = System.currentTimeMillis();
507      boolean balancerRan = !plans.isEmpty();
508      if (balancerRan) {
509        LOG.info("RSGroup balance " + groupName + " starting with plan count: " + plans.size());
510        for (RegionPlan plan: plans) {
511          LOG.info("balance " + plan);
512          assignmentManager.moveAsync(plan);
513        }
514        LOG.info("RSGroup balance " + groupName + " completed after " +
515            (System.currentTimeMillis()-startTime) + " seconds");
516      }
517      return balancerRan;
518    }
519  }
520
521  @Override
522  public List<RSGroupInfo> listRSGroups() throws IOException {
523    return rsGroupInfoManager.listRSGroups();
524  }
525
526  @Override
527  public RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException {
528    return rsGroupInfoManager.getRSGroupOfServer(hostPort);
529  }
530
531  @Override
532  public void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String targetGroup)
533      throws IOException {
534    if (servers == null || servers.isEmpty()) {
535      throw new ConstraintException("The list of servers to move cannot be null or empty.");
536    }
537    if (tables == null || tables.isEmpty()) {
538      throw new ConstraintException("The list of tables to move cannot be null or empty.");
539    }
540
541    //check target group
542    getAndCheckRSGroupInfo(targetGroup);
543
544    // Hold a lock on the manager instance while moving servers and tables to prevent
545    // another writer changing our state while we are working.
546    synchronized (rsGroupInfoManager) {
547      //check servers and tables status
548      checkServersAndTables(servers, tables, targetGroup);
549
550      //Move servers and tables to a new group.
551      String srcGroup = getRSGroupOfServer(servers.iterator().next()).getName();
552      rsGroupInfoManager.moveServersAndTables(servers, tables, srcGroup, targetGroup);
553
554      //move regions which should not belong to these tables
555      moveRegionsFromServers(servers, tables, targetGroup);
556      //move regions which should belong to these servers
557      moveRegionsToServers(servers, tables, targetGroup);
558    }
559    LOG.info("Move servers and tables done. Severs :"
560            + servers + " , Tables : " + tables + " => " +  targetGroup);
561  }
562
563  @Override
564  public void removeServers(Set<Address> servers) throws IOException {
565    {
566      if (servers == null || servers.isEmpty()) {
567        throw new ConstraintException("The set of servers to remove cannot be null or empty.");
568      }
569      // Hold a lock on the manager instance while moving servers to prevent
570      // another writer changing our state while we are working.
571      synchronized (rsGroupInfoManager) {
572        //check the set of servers
573        checkForDeadOrOnlineServers(servers);
574        rsGroupInfoManager.removeServers(servers);
575        LOG.info("Remove decommissioned servers " + servers + " from rsgroup done.");
576      }
577    }
578  }
579
580  private Map<String, RegionState> rsGroupGetRegionsInTransition(String groupName)
581      throws IOException {
582    Map<String, RegionState> rit = Maps.newTreeMap();
583    AssignmentManager am = master.getAssignmentManager();
584    for(TableName tableName : getRSGroupInfo(groupName).getTables()) {
585      for(RegionInfo regionInfo: am.getRegionStates().getRegionsOfTable(tableName)) {
586        RegionState state = am.getRegionStates().getRegionTransitionState(regionInfo);
587        if(state != null) {
588          rit.put(regionInfo.getEncodedName(), state);
589        }
590      }
591    }
592    return rit;
593  }
594
595  private Map<TableName, Map<ServerName, List<RegionInfo>>>
596      getRSGroupAssignmentsByTable(String groupName) throws IOException {
597    Map<TableName, Map<ServerName, List<RegionInfo>>> result = Maps.newHashMap();
598    RSGroupInfo rsGroupInfo = getRSGroupInfo(groupName);
599    Map<TableName, Map<ServerName, List<RegionInfo>>> assignments = Maps.newHashMap();
600    for(Map.Entry<RegionInfo, ServerName> entry:
601        master.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()) {
602      TableName currTable = entry.getKey().getTable();
603      ServerName currServer = entry.getValue();
604      RegionInfo currRegion = entry.getKey();
605      if (rsGroupInfo.getTables().contains(currTable)) {
606        assignments.putIfAbsent(currTable, new HashMap<>());
607        assignments.get(currTable).putIfAbsent(currServer, new ArrayList<>());
608        assignments.get(currTable).get(currServer).add(currRegion);
609      }
610    }
611
612    Map<ServerName, List<RegionInfo>> serverMap = Maps.newHashMap();
613    for(ServerName serverName: master.getServerManager().getOnlineServers().keySet()) {
614      if(rsGroupInfo.getServers().contains(serverName.getAddress())) {
615        serverMap.put(serverName, Collections.emptyList());
616      }
617    }
618
619    // add all tables that are members of the group
620    for(TableName tableName : rsGroupInfo.getTables()) {
621      if(assignments.containsKey(tableName)) {
622        result.put(tableName, new HashMap<>());
623        result.get(tableName).putAll(serverMap);
624        result.get(tableName).putAll(assignments.get(tableName));
625        LOG.debug("Adding assignments for " + tableName + ": " + assignments.get(tableName));
626      }
627    }
628
629    return result;
630  }
631
632  /**
633   * Check if the set of servers are belong to dead servers list or online servers list.
634   * @param servers servers to remove
635   */
636  private void checkForDeadOrOnlineServers(Set<Address> servers) throws ConstraintException {
637    // This uglyness is because we only have Address, not ServerName.
638    Set<Address> onlineServers = new HashSet<>();
639    List<ServerName> drainingServers = master.getServerManager().getDrainingServersList();
640    for (ServerName server : master.getServerManager().getOnlineServers().keySet()) {
641      // Only online but not decommissioned servers are really online
642      if (!drainingServers.contains(server)) {
643        onlineServers.add(server.getAddress());
644      }
645    }
646
647    Set<Address> deadServers = new HashSet<>();
648    for(ServerName server: master.getServerManager().getDeadServers().copyServerNames()) {
649      deadServers.add(server.getAddress());
650    }
651
652    for (Address address: servers) {
653      if (onlineServers.contains(address)) {
654        throw new ConstraintException(
655            "Server " + address + " is an online server, not allowed to remove.");
656      }
657      if (deadServers.contains(address)) {
658        throw new ConstraintException(
659            "Server " + address + " is on the dead servers list,"
660                + " Maybe it will come back again, not allowed to remove.");
661      }
662    }
663  }
664}