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