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