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