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.master.assignment;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.concurrent.ConcurrentSkipListMap;
029import java.util.concurrent.atomic.AtomicInteger;
030import java.util.function.Predicate;
031import java.util.stream.Collectors;
032import org.apache.hadoop.hbase.HRegionLocation;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.client.RegionInfo;
036import org.apache.hadoop.hbase.client.TableState;
037import org.apache.hadoop.hbase.master.RegionState;
038import org.apache.hadoop.hbase.master.RegionState.State;
039import org.apache.hadoop.hbase.master.TableStateManager;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.apache.yetus.audience.InterfaceAudience;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045/**
046 * RegionStates contains a set of Maps that describes the in-memory state of the AM, with the
047 * regions available in the system, the region in transition, the offline regions and the servers
048 * holding regions.
049 */
050@InterfaceAudience.Private
051public class RegionStates {
052  private static final Logger LOG = LoggerFactory.getLogger(RegionStates.class);
053
054  private final Object regionsMapLock = new Object();
055
056  private final AtomicInteger activeTransitProcedureCount = new AtomicInteger(0);
057
058  // TODO: Replace the ConcurrentSkipListMaps
059  /**
060   * A Map from {@link RegionInfo#getRegionName()} to {@link RegionStateNode}
061   */
062  private final ConcurrentSkipListMap<byte[], RegionStateNode> regionsMap =
063    new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR);
064
065  /**
066   * this map is a hack to lookup of region in master by encoded region name is O(n). must put and
067   * remove with regionsMap.
068   */
069  private final ConcurrentSkipListMap<String, RegionStateNode> encodedRegionsMap =
070    new ConcurrentSkipListMap<>();
071
072  /**
073   * Regions marked as offline on a read of hbase:meta. Unused or at least, once offlined, regions
074   * have no means of coming on line again. TODO.
075   */
076  private final ConcurrentSkipListMap<RegionInfo, RegionStateNode> regionOffline =
077    new ConcurrentSkipListMap<RegionInfo, RegionStateNode>();
078
079  private final ConcurrentSkipListMap<byte[], RegionFailedOpen> regionFailedOpen =
080    new ConcurrentSkipListMap<byte[], RegionFailedOpen>(Bytes.BYTES_COMPARATOR);
081
082  private final ConcurrentHashMap<ServerName, ServerStateNode> serverMap =
083    new ConcurrentHashMap<ServerName, ServerStateNode>();
084
085  public RegionStates() {
086  }
087
088  /**
089   * Called on stop of AssignmentManager.
090   */
091  public void clear() {
092    regionsMap.clear();
093    encodedRegionsMap.clear();
094    regionOffline.clear();
095    serverMap.clear();
096  }
097
098  // ==========================================================================
099  // RegionStateNode helpers
100  // ==========================================================================
101  RegionStateNode createRegionStateNode(RegionInfo regionInfo) {
102    synchronized (regionsMapLock) {
103      RegionStateNode node = regionsMap.computeIfAbsent(regionInfo.getRegionName(),
104        key -> new RegionStateNode(regionInfo, activeTransitProcedureCount));
105
106      if (encodedRegionsMap.get(regionInfo.getEncodedName()) != node) {
107        encodedRegionsMap.put(regionInfo.getEncodedName(), node);
108      }
109
110      return node;
111    }
112  }
113
114  public RegionStateNode getOrCreateRegionStateNode(RegionInfo regionInfo) {
115    RegionStateNode node = getRegionStateNodeFromName(regionInfo.getRegionName());
116    return node != null ? node : createRegionStateNode(regionInfo);
117  }
118
119  public RegionStateNode getRegionStateNodeFromName(byte[] regionName) {
120    return regionsMap.get(regionName);
121  }
122
123  public RegionStateNode getRegionStateNodeFromEncodedRegionName(final String encodedRegionName) {
124    return encodedRegionsMap.get(encodedRegionName);
125  }
126
127  public RegionStateNode getRegionStateNode(RegionInfo regionInfo) {
128    return getRegionStateNodeFromName(regionInfo.getRegionName());
129  }
130
131  public void deleteRegion(final RegionInfo regionInfo) {
132    synchronized (regionsMapLock) {
133      regionsMap.remove(regionInfo.getRegionName());
134      encodedRegionsMap.remove(regionInfo.getEncodedName());
135    }
136    // Remove from the offline regions map too if there.
137    if (this.regionOffline.containsKey(regionInfo)) {
138      if (LOG.isTraceEnabled()) LOG.trace("Removing from regionOffline Map: " + regionInfo);
139      this.regionOffline.remove(regionInfo);
140    }
141  }
142
143  public void deleteRegions(final List<RegionInfo> regionInfos) {
144    regionInfos.forEach(this::deleteRegion);
145  }
146
147  List<RegionStateNode> getTableRegionStateNodes(final TableName tableName) {
148    final ArrayList<RegionStateNode> regions = new ArrayList<RegionStateNode>();
149    for (RegionStateNode node : regionsMap.tailMap(tableName.getName()).values()) {
150      if (!node.getTable().equals(tableName)) break;
151      regions.add(node);
152    }
153    return regions;
154  }
155
156  ArrayList<RegionState> getTableRegionStates(final TableName tableName) {
157    final ArrayList<RegionState> regions = new ArrayList<RegionState>();
158    for (RegionStateNode node : regionsMap.tailMap(tableName.getName()).values()) {
159      if (!node.getTable().equals(tableName)) break;
160      regions.add(node.toRegionState());
161    }
162    return regions;
163  }
164
165  ArrayList<RegionInfo> getTableRegionsInfo(final TableName tableName) {
166    final ArrayList<RegionInfo> regions = new ArrayList<RegionInfo>();
167    for (RegionStateNode node : regionsMap.tailMap(tableName.getName()).values()) {
168      if (!node.getTable().equals(tableName)) break;
169      regions.add(node.getRegionInfo());
170    }
171    return regions;
172  }
173
174  /** Returns A view of region state nodes for all the regions. */
175  public Collection<RegionStateNode> getRegionStateNodes() {
176    return Collections.unmodifiableCollection(regionsMap.values());
177  }
178
179  /** Returns A snapshot of region state nodes for all the regions. */
180  public ArrayList<RegionState> getRegionStates() {
181    final ArrayList<RegionState> regions = new ArrayList<>(regionsMap.size());
182    for (RegionStateNode node : regionsMap.values()) {
183      regions.add(node.toRegionState());
184    }
185    return regions;
186  }
187
188  // ==========================================================================
189  // RegionState helpers
190  // ==========================================================================
191  public RegionState getRegionState(final RegionInfo regionInfo) {
192    RegionStateNode regionStateNode = getRegionStateNode(regionInfo);
193    return regionStateNode == null ? null : regionStateNode.toRegionState();
194  }
195
196  public RegionState getRegionState(final String encodedRegionName) {
197    final RegionStateNode node = encodedRegionsMap.get(encodedRegionName);
198    if (node == null) {
199      return null;
200    }
201    return node.toRegionState();
202  }
203
204  // ============================================================================================
205  // TODO: helpers
206  // ============================================================================================
207  public boolean hasTableRegionStates(final TableName tableName) {
208    // TODO
209    return !getTableRegionStates(tableName).isEmpty();
210  }
211
212  /** Returns Return online regions of table; does not include OFFLINE or SPLITTING regions. */
213  public List<RegionInfo> getRegionsOfTable(TableName table) {
214    return getRegionsOfTable(table, regionNode -> !regionNode.isInState(State.OFFLINE, State.SPLIT)
215      && !regionNode.getRegionInfo().isSplitParent());
216  }
217
218  private HRegionLocation createRegionForReopen(RegionStateNode node) {
219    node.lock();
220    try {
221      if (!include(node, false)) {
222        return null;
223      }
224      if (node.isInState(State.OPEN)) {
225        return new HRegionLocation(node.getRegionInfo(), node.getRegionLocation(),
226          node.getOpenSeqNum());
227      } else if (node.isInState(State.OPENING)) {
228        return new HRegionLocation(node.getRegionInfo(), node.getRegionLocation(), -1);
229      } else {
230        return null;
231      }
232    } finally {
233      node.unlock();
234    }
235  }
236
237  /**
238   * Get the regions to be reopened when modifying a table.
239   * <p/>
240   * Notice that the {@code openSeqNum} in the returned HRegionLocation is also used to indicate the
241   * state of this region, positive means the region is in {@link State#OPEN}, -1 means
242   * {@link State#OPENING}. And for regions in other states we do not need reopen them.
243   */
244  public List<HRegionLocation> getRegionsOfTableForReopen(TableName tableName) {
245    return getTableRegionStateNodes(tableName).stream().map(this::createRegionForReopen)
246      .filter(r -> r != null).collect(Collectors.toList());
247  }
248
249  /**
250   * Check whether the region has been reopened. The meaning of the {@link HRegionLocation} is the
251   * same with {@link #getRegionsOfTableForReopen(TableName)}.
252   * <p/>
253   * For a region which is in {@link State#OPEN} before, if the region state is changed or the open
254   * seq num is changed, we can confirm that it has been reopened.
255   * <p/>
256   * For a region which is in {@link State#OPENING} before, usually it will be in {@link State#OPEN}
257   * now and we will schedule a MRP to reopen it. But there are several exceptions:
258   * <ul>
259   * <li>The region is in state other than {@link State#OPEN} or {@link State#OPENING}.</li>
260   * <li>The location of the region has been changed</li>
261   * </ul>
262   * Of course the region could still be in {@link State#OPENING} state and still on the same
263   * server, then here we will still return a {@link HRegionLocation} for it, just like
264   * {@link #getRegionsOfTableForReopen(TableName)}.
265   * @param oldLoc the previous state/location of this region
266   * @return null if the region has been reopened, otherwise a new {@link HRegionLocation} which
267   *         means we still need to reopen the region.
268   * @see #getRegionsOfTableForReopen(TableName)
269   */
270  public HRegionLocation checkReopened(HRegionLocation oldLoc) {
271    RegionStateNode node = getRegionStateNode(oldLoc.getRegion());
272    // HBASE-20921
273    // if the oldLoc's state node does not exist, that means the region is
274    // merged or split, no need to check it
275    if (node == null) {
276      return null;
277    }
278    node.lock();
279    try {
280      if (oldLoc.getSeqNum() >= 0) {
281        // in OPEN state before
282        if (node.isInState(State.OPEN)) {
283          if (node.getOpenSeqNum() > oldLoc.getSeqNum()) {
284            // normal case, the region has been reopened
285            return null;
286          } else {
287            // the open seq num does not change, need to reopen again
288            return new HRegionLocation(node.getRegionInfo(), node.getRegionLocation(),
289              node.getOpenSeqNum());
290          }
291        } else {
292          // the state has been changed so we can make sure that the region has been reopened(not
293          // finished maybe, but not a problem).
294          return null;
295        }
296      } else {
297        // in OPENING state before
298        if (!node.isInState(State.OPEN, State.OPENING)) {
299          // not in OPEN or OPENING state, then we can make sure that the region has been
300          // reopened(not finished maybe, but not a problem)
301          return null;
302        } else {
303          if (!node.getRegionLocation().equals(oldLoc.getServerName())) {
304            // the region has been moved, so we can make sure that the region has been reopened.
305            return null;
306          }
307          // normal case, we are still in OPENING state, or the reopen has been opened and the state
308          // is changed to OPEN.
309          long openSeqNum = node.isInState(State.OPEN) ? node.getOpenSeqNum() : -1;
310          return new HRegionLocation(node.getRegionInfo(), node.getRegionLocation(), openSeqNum);
311        }
312      }
313    } finally {
314      node.unlock();
315    }
316  }
317
318  /**
319   * Get the regions for enabling a table.
320   * <p/>
321   * Here we want the EnableTableProcedure to be more robust and can be used to fix some nasty
322   * states, so the checks in this method will be a bit strange. In general, a region can only be
323   * offline when it is split, for merging we will just delete the parent regions, but with HBCK we
324   * may force update the state of a region to fix some nasty bugs, so in this method we will try to
325   * bring the offline regions back if it is not split. That's why we only check for split state
326   * here.
327   */
328  public List<RegionInfo> getRegionsOfTableForEnabling(TableName table) {
329    return getRegionsOfTable(table,
330      regionNode -> !regionNode.isInState(State.SPLIT) && !regionNode.getRegionInfo().isSplit());
331  }
332
333  /**
334   * Get the regions for deleting a table.
335   * <p/>
336   * Here we need to return all the regions irrespective of the states in order to archive them all.
337   * This is because if we don't archive OFFLINE/SPLIT regions and if a snapshot or a cloned table
338   * references to the regions, we will lose the data of the regions.
339   */
340  public List<RegionInfo> getRegionsOfTableForDeleting(TableName table) {
341    return getTableRegionStateNodes(table).stream().map(RegionStateNode::getRegionInfo)
342      .collect(Collectors.toList());
343  }
344
345  /** Returns Return the regions of the table and filter them. */
346  private List<RegionInfo> getRegionsOfTable(TableName table, Predicate<RegionStateNode> filter) {
347    return getTableRegionStateNodes(table).stream().filter(filter).map(n -> n.getRegionInfo())
348      .collect(Collectors.toList());
349  }
350
351  /**
352   * Utility. Whether to include region in list of regions. Default is to weed out split and offline
353   * regions.
354   * @return True if we should include the <code>node</code> (do not include if split or offline
355   *         unless <code>offline</code> is set to true.
356   */
357  private boolean include(final RegionStateNode node, final boolean offline) {
358    if (LOG.isTraceEnabled()) {
359      LOG.trace("WORKING ON " + node + " " + node.getRegionInfo());
360    }
361    final RegionInfo hri = node.getRegionInfo();
362    if (node.isInState(State.SPLIT) || hri.isSplit()) {
363      return false;
364    }
365    if ((node.isInState(State.OFFLINE) || hri.isOffline()) && !offline) {
366      return false;
367    }
368    return (!hri.isOffline() && !hri.isSplit()) || ((hri.isOffline() || hri.isSplit()) && offline);
369  }
370
371  // ============================================================================================
372  // Split helpers
373  // These methods will only be called in ServerCrashProcedure, and at the end of SCP we will remove
374  // the ServerStateNode by calling removeServer.
375  // ============================================================================================
376
377  private void setServerState(ServerName serverName, ServerState state) {
378    ServerStateNode serverNode = getServerNode(serverName);
379    synchronized (serverNode) {
380      serverNode.setState(state);
381    }
382  }
383
384  /**
385   * Call this when we start meta log splitting a crashed Server.
386   * @see #metaLogSplit(ServerName)
387   */
388  public void metaLogSplitting(ServerName serverName) {
389    setServerState(serverName, ServerState.SPLITTING_META);
390  }
391
392  /**
393   * Called after we've split the meta logs on a crashed Server.
394   * @see #metaLogSplitting(ServerName)
395   */
396  public void metaLogSplit(ServerName serverName) {
397    setServerState(serverName, ServerState.SPLITTING_META_DONE);
398  }
399
400  /**
401   * Call this when we start log splitting for a crashed Server.
402   * @see #logSplit(ServerName)
403   */
404  public void logSplitting(final ServerName serverName) {
405    setServerState(serverName, ServerState.SPLITTING);
406  }
407
408  /**
409   * Called after we've split all logs on a crashed Server.
410   * @see #logSplitting(ServerName)
411   */
412  public void logSplit(final ServerName serverName) {
413    setServerState(serverName, ServerState.OFFLINE);
414  }
415
416  public void updateRegionState(RegionInfo regionInfo, State state) {
417    RegionStateNode regionNode = getOrCreateRegionStateNode(regionInfo);
418    regionNode.lock();
419    try {
420      regionNode.setState(state);
421    } finally {
422      regionNode.unlock();
423    }
424  }
425
426  // ============================================================================================
427  // TODO:
428  // ============================================================================================
429  public List<RegionInfo> getAssignedRegions() {
430    final List<RegionInfo> result = new ArrayList<RegionInfo>();
431    for (RegionStateNode node : regionsMap.values()) {
432      if (!node.isTransitionScheduled()) {
433        result.add(node.getRegionInfo());
434      }
435    }
436    return result;
437  }
438
439  public boolean isRegionInState(RegionInfo regionInfo, State... state) {
440    RegionStateNode regionNode = getRegionStateNode(regionInfo);
441    if (regionNode != null) {
442      regionNode.lock();
443      try {
444        return regionNode.isInState(state);
445      } finally {
446        regionNode.unlock();
447      }
448    }
449    return false;
450  }
451
452  public boolean isRegionOnline(final RegionInfo regionInfo) {
453    return isRegionInState(regionInfo, State.OPEN);
454  }
455
456  /** Returns True if region is offline (In OFFLINE or CLOSED state). */
457  public boolean isRegionOffline(final RegionInfo regionInfo) {
458    return isRegionInState(regionInfo, State.OFFLINE, State.CLOSED);
459  }
460
461  public Map<ServerName, List<RegionInfo>>
462    getSnapShotOfAssignment(final Collection<RegionInfo> regions) {
463    final Map<ServerName, List<RegionInfo>> result = new HashMap<ServerName, List<RegionInfo>>();
464    if (regions != null) {
465      for (RegionInfo hri : regions) {
466        final RegionStateNode node = getRegionStateNode(hri);
467        if (node == null) {
468          continue;
469        }
470        createSnapshot(node, result);
471      }
472    } else {
473      for (RegionStateNode node : regionsMap.values()) {
474        if (node == null) {
475          continue;
476        }
477        createSnapshot(node, result);
478      }
479    }
480    return result;
481  }
482
483  private void createSnapshot(RegionStateNode node, Map<ServerName, List<RegionInfo>> result) {
484    final ServerName serverName = node.getRegionLocation();
485    if (serverName == null) {
486      return;
487    }
488
489    List<RegionInfo> serverRegions = result.get(serverName);
490    if (serverRegions == null) {
491      serverRegions = new ArrayList<RegionInfo>();
492      result.put(serverName, serverRegions);
493    }
494    serverRegions.add(node.getRegionInfo());
495  }
496
497  public Map<RegionInfo, ServerName> getRegionAssignments() {
498    final HashMap<RegionInfo, ServerName> assignments = new HashMap<RegionInfo, ServerName>();
499    for (RegionStateNode node : regionsMap.values()) {
500      assignments.put(node.getRegionInfo(), node.getRegionLocation());
501    }
502    return assignments;
503  }
504
505  public Map<RegionState.State, List<RegionInfo>> getRegionByStateOfTable(TableName tableName) {
506    final State[] states = State.values();
507    final Map<RegionState.State, List<RegionInfo>> tableRegions =
508      new HashMap<State, List<RegionInfo>>(states.length);
509    for (int i = 0; i < states.length; ++i) {
510      tableRegions.put(states[i], new ArrayList<RegionInfo>());
511    }
512
513    for (RegionStateNode node : regionsMap.values()) {
514      if (node.getTable().equals(tableName)) {
515        tableRegions.get(node.getState()).add(node.getRegionInfo());
516      }
517    }
518    return tableRegions;
519  }
520
521  public ServerName getRegionServerOfRegion(RegionInfo regionInfo) {
522    RegionStateNode regionNode = getRegionStateNode(regionInfo);
523    if (regionNode != null) {
524      regionNode.lock();
525      try {
526        ServerName server = regionNode.getRegionLocation();
527        return server != null ? server : regionNode.getLastHost();
528      } finally {
529        regionNode.unlock();
530      }
531    }
532    return null;
533  }
534
535  /**
536   * This is an EXPENSIVE clone. Cloning though is the safest thing to do. Can't let out original
537   * since it can change and at least the load balancer wants to iterate this exported list. We need
538   * to synchronize on regions since all access to this.servers is under a lock on this.regions.
539   * @return A clone of current open or opening assignments.
540   */
541  public Map<TableName, Map<ServerName, List<RegionInfo>>>
542    getAssignmentsForBalancer(TableStateManager tableStateManager, List<ServerName> onlineServers) {
543    final Map<TableName, Map<ServerName, List<RegionInfo>>> result = new HashMap<>();
544    for (RegionStateNode node : regionsMap.values()) {
545      // DisableTableProcedure first sets the table state to DISABLED and then force unassigns
546      // the regions in a loop. The balancer should ignore all regions for tables in DISABLED
547      // state because even if still currently open we expect them to be offlined very soon.
548      if (isTableDisabled(tableStateManager, node.getTable())) {
549        if (LOG.isTraceEnabled()) {
550          LOG.trace("Ignoring {} because table is disabled", node);
551        }
552        continue;
553      }
554      // When balancing, we are only interested in OPEN or OPENING regions. These can be
555      // expected to remain online until the next balancer iteration or unless the balancer
556      // decides to move it. Regions in other states are not eligible for balancing, because
557      // they are closing, splitting, merging, or otherwise already in transition.
558      if (!node.isInState(State.OPEN, State.OPENING)) {
559        if (LOG.isTraceEnabled()) {
560          LOG.trace("Ignoring {} because region is not OPEN or OPENING", node);
561        }
562        continue;
563      }
564      Map<ServerName, List<RegionInfo>> tableResult =
565        result.computeIfAbsent(node.getTable(), t -> new HashMap<>());
566      final ServerName serverName = node.getRegionLocation();
567      // A region in ONLINE or OPENING state should have a location.
568      if (serverName == null) {
569        LOG.warn("Skipping, no server for {}", node);
570        continue;
571      }
572      List<RegionInfo> serverResult =
573        tableResult.computeIfAbsent(serverName, s -> new ArrayList<>());
574      serverResult.add(node.getRegionInfo());
575    }
576    // Add online servers with no assignment for the table.
577    for (Map<ServerName, List<RegionInfo>> table : result.values()) {
578      for (ServerName serverName : onlineServers) {
579        table.computeIfAbsent(serverName, key -> new ArrayList<>());
580      }
581    }
582    return result;
583  }
584
585  private boolean isTableDisabled(final TableStateManager tableStateManager,
586    final TableName tableName) {
587    return tableStateManager.isTableState(tableName, TableState.State.DISABLED,
588      TableState.State.DISABLING);
589  }
590
591  // ==========================================================================
592  // Region offline helpers
593  // ==========================================================================
594  // TODO: Populated when we read meta but regions never make it out of here.
595  public void addToOfflineRegions(final RegionStateNode regionNode) {
596    LOG.info("Added to offline, CURRENTLY NEVER CLEARED!!! " + regionNode);
597    regionOffline.put(regionNode.getRegionInfo(), regionNode);
598  }
599
600  public int getRegionTransitScheduledCount() {
601    return activeTransitProcedureCount.get();
602  }
603
604  // ==========================================================================
605  // Region FAIL_OPEN helpers
606  // ==========================================================================
607  public static final class RegionFailedOpen {
608    private final RegionStateNode regionNode;
609
610    private volatile Exception exception = null;
611    private AtomicInteger retries = new AtomicInteger();
612
613    public RegionFailedOpen(final RegionStateNode regionNode) {
614      this.regionNode = regionNode;
615    }
616
617    public RegionStateNode getRegionStateNode() {
618      return regionNode;
619    }
620
621    public RegionInfo getRegionInfo() {
622      return regionNode.getRegionInfo();
623    }
624
625    public int incrementAndGetRetries() {
626      return this.retries.incrementAndGet();
627    }
628
629    public int getRetries() {
630      return retries.get();
631    }
632
633    public void setException(final Exception exception) {
634      this.exception = exception;
635    }
636
637    public Exception getException() {
638      return this.exception;
639    }
640  }
641
642  public RegionFailedOpen addToFailedOpen(final RegionStateNode regionNode) {
643    final byte[] key = regionNode.getRegionInfo().getRegionName();
644    return regionFailedOpen.computeIfAbsent(key, (k) -> new RegionFailedOpen(regionNode));
645  }
646
647  public RegionFailedOpen getFailedOpen(final RegionInfo regionInfo) {
648    return regionFailedOpen.get(regionInfo.getRegionName());
649  }
650
651  public void removeFromFailedOpen(final RegionInfo regionInfo) {
652    regionFailedOpen.remove(regionInfo.getRegionName());
653  }
654
655  public List<RegionState> getRegionFailedOpen() {
656    if (regionFailedOpen.isEmpty()) return Collections.emptyList();
657
658    ArrayList<RegionState> regions = new ArrayList<RegionState>(regionFailedOpen.size());
659    for (RegionFailedOpen r : regionFailedOpen.values()) {
660      regions.add(r.getRegionStateNode().toRegionState());
661    }
662    return regions;
663  }
664
665  // ==========================================================================
666  // Servers
667  // ==========================================================================
668
669  /**
670   * Create the ServerStateNode when registering a new region server
671   */
672  public void createServer(ServerName serverName) {
673    serverMap.computeIfAbsent(serverName, key -> new ServerStateNode(key));
674  }
675
676  /**
677   * Called by SCP at end of successful processing.
678   */
679  public void removeServer(ServerName serverName) {
680    serverMap.remove(serverName);
681  }
682
683  /** Returns Pertinent ServerStateNode or NULL if none found (Do not make modifications). */
684  public ServerStateNode getServerNode(final ServerName serverName) {
685    return serverMap.get(serverName);
686  }
687
688  public double getAverageLoad() {
689    int numServers = 0;
690    int totalLoad = 0;
691    for (ServerStateNode node : serverMap.values()) {
692      totalLoad += node.getRegionCount();
693      numServers++;
694    }
695    return numServers == 0 ? 0.0 : (double) totalLoad / (double) numServers;
696  }
697
698  public void addRegionToServer(final RegionStateNode regionNode) {
699    ServerStateNode serverNode = getServerNode(regionNode.getRegionLocation());
700    serverNode.addRegion(regionNode);
701  }
702
703  public void removeRegionFromServer(final ServerName serverName,
704    final RegionStateNode regionNode) {
705    ServerStateNode serverNode = getServerNode(serverName);
706    // here the server node could be null. For example, if there is already a TRSP for a region and
707    // at the same time, the target server is crashed and there is a SCP. The SCP will interrupt the
708    // TRSP and the TRSP will first set the region as abnormally closed and remove it from the
709    // server node. But here, this TRSP is not a child procedure of the SCP, so it is possible that
710    // the SCP finishes, thus removes the server node for this region server, before the TRSP wakes
711    // up and enter here to remove the region node from the server node, then we will get a null
712    // server node here.
713    if (serverNode != null) {
714      serverNode.removeRegion(regionNode);
715    }
716  }
717
718  // ==========================================================================
719  // ToString helpers
720  // ==========================================================================
721  public static String regionNamesToString(final Collection<byte[]> regions) {
722    final StringBuilder sb = new StringBuilder();
723    final Iterator<byte[]> it = regions.iterator();
724    sb.append("[");
725    if (it.hasNext()) {
726      sb.append(Bytes.toStringBinary(it.next()));
727      while (it.hasNext()) {
728        sb.append(", ");
729        sb.append(Bytes.toStringBinary(it.next()));
730      }
731    }
732    sb.append("]");
733    return sb.toString();
734  }
735}