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.HConstants;
036import org.apache.hadoop.hbase.HRegionLocation;
037import org.apache.hadoop.hbase.ServerName;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.client.RegionInfo;
040import org.apache.hadoop.hbase.client.TableState;
041import org.apache.hadoop.hbase.master.RegionState;
042import org.apache.hadoop.hbase.master.RegionState.State;
043import org.apache.hadoop.hbase.master.TableStateManager;
044import org.apache.hadoop.hbase.util.Bytes;
045import org.apache.yetus.audience.InterfaceAudience;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
050
051/**
052 * RegionStates contains a set of Maps that describes the in-memory state of the AM, with
053 * the regions available in the system, the region in transition, the offline regions and
054 * the servers holding regions.
055 */
056@InterfaceAudience.Private
057public class RegionStates {
058  private static final Logger LOG = LoggerFactory.getLogger(RegionStates.class);
059
060  // This comparator sorts the RegionStates by time stamp then Region name.
061  // Comparing by timestamp alone can lead us to discard different RegionStates that happen
062  // to share a timestamp.
063  private static class RegionStateStampComparator implements Comparator<RegionState> {
064    @Override
065    public int compare(final RegionState l, final RegionState r) {
066      int stampCmp = Long.compare(l.getStamp(), r.getStamp());
067      return stampCmp != 0 ? stampCmp : RegionInfo.COMPARATOR.compare(l.getRegion(), r.getRegion());
068    }
069  }
070
071  public final static RegionStateStampComparator REGION_STATE_STAMP_COMPARATOR =
072      new RegionStateStampComparator();
073
074  // TODO: Replace the ConcurrentSkipListMaps
075  /**
076   * RegionName -- i.e. RegionInfo.getRegionName() -- as bytes to {@link RegionStateNode}
077   */
078  private final ConcurrentSkipListMap<byte[], RegionStateNode> regionsMap =
079      new ConcurrentSkipListMap<byte[], RegionStateNode>(Bytes.BYTES_COMPARATOR);
080
081  private final ConcurrentSkipListMap<RegionInfo, RegionStateNode> regionInTransition =
082    new ConcurrentSkipListMap<RegionInfo, RegionStateNode>(RegionInfo.COMPARATOR);
083
084  /**
085   * Regions marked as offline on a read of hbase:meta. Unused or at least, once
086   * offlined, regions have no means of coming on line again. TODO.
087   */
088  private final ConcurrentSkipListMap<RegionInfo, RegionStateNode> regionOffline =
089    new ConcurrentSkipListMap<RegionInfo, RegionStateNode>();
090
091  private final ConcurrentSkipListMap<byte[], RegionFailedOpen> regionFailedOpen =
092    new ConcurrentSkipListMap<byte[], RegionFailedOpen>(Bytes.BYTES_COMPARATOR);
093
094  private final ConcurrentHashMap<ServerName, ServerStateNode> serverMap =
095      new ConcurrentHashMap<ServerName, ServerStateNode>();
096
097  public RegionStates() { }
098
099  public void clear() {
100    regionsMap.clear();
101    regionInTransition.clear();
102    regionOffline.clear();
103    serverMap.clear();
104  }
105
106  @VisibleForTesting
107  public boolean isRegionInRegionStates(final RegionInfo hri) {
108    return (regionsMap.containsKey(hri.getRegionName()) || regionInTransition.containsKey(hri)
109        || regionOffline.containsKey(hri));
110  }
111
112  // ==========================================================================
113  //  RegionStateNode helpers
114  // ==========================================================================
115  @VisibleForTesting
116  RegionStateNode createRegionStateNode(RegionInfo regionInfo) {
117    RegionStateNode newNode = new RegionStateNode(regionInfo, regionInTransition);
118    RegionStateNode oldNode = regionsMap.putIfAbsent(regionInfo.getRegionName(), newNode);
119    return oldNode != null ? oldNode : newNode;
120  }
121
122  public RegionStateNode getOrCreateRegionStateNode(RegionInfo regionInfo) {
123    RegionStateNode node = getRegionStateNodeFromName(regionInfo.getRegionName());
124    return node != null ? node : createRegionStateNode(regionInfo);
125  }
126
127  RegionStateNode getRegionStateNodeFromName(byte[] regionName) {
128    return regionsMap.get(regionName);
129  }
130
131  public RegionStateNode getRegionStateNode(RegionInfo regionInfo) {
132    return getRegionStateNodeFromName(regionInfo.getRegionName());
133  }
134
135  public void deleteRegion(final RegionInfo regionInfo) {
136    regionsMap.remove(regionInfo.getRegionName());
137    // See HBASE-20860
138    // After master restarts, merged regions' RIT state may not be cleaned,
139    // making sure they are cleaned here
140    if (regionInTransition.containsKey(regionInfo)) {
141      regionInTransition.remove(regionInfo);
142    }
143    // Remove from the offline regions map too if there.
144    if (this.regionOffline.containsKey(regionInfo)) {
145      if (LOG.isTraceEnabled()) LOG.trace("Removing from regionOffline Map: " + regionInfo);
146      this.regionOffline.remove(regionInfo);
147    }
148  }
149
150  public void deleteRegions(final List<RegionInfo> regionInfos) {
151    regionInfos.forEach(this::deleteRegion);
152  }
153
154  ArrayList<RegionStateNode> getTableRegionStateNodes(final TableName tableName) {
155    final ArrayList<RegionStateNode> regions = new ArrayList<RegionStateNode>();
156    for (RegionStateNode node: regionsMap.tailMap(tableName.getName()).values()) {
157      if (!node.getTable().equals(tableName)) break;
158      regions.add(node);
159    }
160    return regions;
161  }
162
163  ArrayList<RegionState> getTableRegionStates(final TableName tableName) {
164    final ArrayList<RegionState> regions = new ArrayList<RegionState>();
165    for (RegionStateNode node: regionsMap.tailMap(tableName.getName()).values()) {
166      if (!node.getTable().equals(tableName)) break;
167      regions.add(node.toRegionState());
168    }
169    return regions;
170  }
171
172  ArrayList<RegionInfo> getTableRegionsInfo(final TableName tableName) {
173    final ArrayList<RegionInfo> regions = new ArrayList<RegionInfo>();
174    for (RegionStateNode node: regionsMap.tailMap(tableName.getName()).values()) {
175      if (!node.getTable().equals(tableName)) break;
176      regions.add(node.getRegionInfo());
177    }
178    return regions;
179  }
180
181  /** @return A view of region state nodes for all the regions. */
182  public Collection<RegionStateNode> getRegionStateNodes() {
183    return Collections.unmodifiableCollection(regionsMap.values());
184  }
185
186  /** @return A snapshot of region state nodes for all the regions. */
187  public ArrayList<RegionState> getRegionStates() {
188    final ArrayList<RegionState> regions = new ArrayList<>(regionsMap.size());
189    for (RegionStateNode node: regionsMap.values()) {
190      regions.add(node.toRegionState());
191    }
192    return regions;
193  }
194
195  // ==========================================================================
196  //  RegionState helpers
197  // ==========================================================================
198  public RegionState getRegionState(final RegionInfo regionInfo) {
199    RegionStateNode regionStateNode = getRegionStateNode(regionInfo);
200    return regionStateNode == null ? null : regionStateNode.toRegionState();
201  }
202
203  public RegionState getRegionState(final String encodedRegionName) {
204    // TODO: Need a map <encodedName, ...> but it is just dispatch merge...
205    for (RegionStateNode node: regionsMap.values()) {
206      if (node.getRegionInfo().getEncodedName().equals(encodedRegionName)) {
207        return node.toRegionState();
208      }
209    }
210    return null;
211  }
212
213  // ============================================================================================
214  //  TODO: helpers
215  // ============================================================================================
216  public boolean hasTableRegionStates(final TableName tableName) {
217    // TODO
218    return !getTableRegionStates(tableName).isEmpty();
219  }
220
221  /**
222   * @return Return online regions of table; does not include OFFLINE or SPLITTING regions.
223   */
224  public List<RegionInfo> getRegionsOfTable(final TableName table) {
225    return getRegionsOfTable(table, false);
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   * @return Return online regions of table; does not include OFFLINE or SPLITTING regions.
330   */
331  public List<RegionInfo> getRegionsOfTable(TableName table, boolean offline) {
332    return getRegionsOfTable(table, state -> include(state, offline));
333  }
334
335  /**
336   * @return Return the regions of the table; does not include OFFLINE unless you set
337   *         <code>offline</code> to true. Does not include regions that are in the
338   *         {@link State#SPLIT} state.
339   */
340  private List<RegionInfo> getRegionsOfTable(TableName table, Predicate<RegionStateNode> filter) {
341    return getTableRegionStateNodes(table).stream().filter(filter).map(n -> n.getRegionInfo())
342      .collect(Collectors.toList());
343  }
344
345  /**
346   * Utility. Whether to include region in list of regions. Default is to
347   * weed out split and offline regions.
348   * @return True if we should include the <code>node</code> (do not include
349   * if split or offline unless <code>offline</code> is set to true.
350   */
351  boolean include(final RegionStateNode node, final boolean offline) {
352    if (LOG.isTraceEnabled()) {
353      LOG.trace("WORKING ON " + node + " " + node.getRegionInfo());
354    }
355    final RegionInfo hri = node.getRegionInfo();
356    if (node.isInState(State.SPLIT) || hri.isSplit()) {
357      return false;
358    }
359    if ((node.isInState(State.OFFLINE) || hri.isOffline()) && !offline) {
360      return false;
361    }
362    return (!hri.isOffline() && !hri.isSplit()) ||
363        ((hri.isOffline() || hri.isSplit()) && offline);
364  }
365
366  // ============================================================================================
367  // Split helpers
368  // These methods will only be called in ServerCrashProcedure, and at the end of SCP we will remove
369  // the ServerStateNode by calling removeServer.
370  // ============================================================================================
371
372  private void setServerState(ServerName serverName, ServerState state) {
373    ServerStateNode serverNode = getOrCreateServer(serverName);
374    synchronized (serverNode) {
375      serverNode.setState(state);
376    }
377  }
378
379  /**
380   * Call this when we start meta log splitting a crashed Server.
381   * @see #metaLogSplit(ServerName)
382   */
383  public void metaLogSplitting(ServerName serverName) {
384    setServerState(serverName, ServerState.SPLITTING_META);
385  }
386
387  /**
388   * Called after we've split the meta logs on a crashed Server.
389   * @see #metaLogSplitting(ServerName)
390   */
391  public void metaLogSplit(ServerName serverName) {
392    setServerState(serverName, ServerState.SPLITTING_META_DONE);
393  }
394
395  /**
396   * Call this when we start log splitting for a crashed Server.
397   * @see #logSplit(ServerName)
398   */
399  public void logSplitting(final ServerName serverName) {
400    setServerState(serverName, ServerState.SPLITTING);
401  }
402
403  /**
404   * Called after we've split all logs on a crashed Server.
405   * @see #logSplitting(ServerName)
406   */
407  public void logSplit(final ServerName serverName) {
408    setServerState(serverName, ServerState.OFFLINE);
409  }
410
411  public void updateRegionState(RegionInfo regionInfo, State state) {
412    RegionStateNode regionNode = getOrCreateRegionStateNode(regionInfo);
413    regionNode.lock();
414    try {
415      regionNode.setState(state);
416    } finally {
417      regionNode.unlock();
418    }
419  }
420
421  // ============================================================================================
422  //  TODO:
423  // ============================================================================================
424  public List<RegionInfo> getAssignedRegions() {
425    final List<RegionInfo> result = new ArrayList<RegionInfo>();
426    for (RegionStateNode node: regionsMap.values()) {
427      if (!node.isInTransition()) {
428        result.add(node.getRegionInfo());
429      }
430    }
431    return result;
432  }
433
434  public boolean isRegionInState(RegionInfo regionInfo, State... state) {
435    RegionStateNode regionNode = getRegionStateNode(regionInfo);
436    if (regionNode != null) {
437      regionNode.lock();
438      try {
439        return regionNode.isInState(state);
440      } finally {
441        regionNode.unlock();
442      }
443    }
444    return false;
445  }
446
447  public boolean isRegionOnline(final RegionInfo regionInfo) {
448    return isRegionInState(regionInfo, State.OPEN);
449  }
450
451  /**
452   * @return True if region is offline (In OFFLINE or CLOSED state).
453   */
454  public boolean isRegionOffline(final RegionInfo regionInfo) {
455    return isRegionInState(regionInfo, State.OFFLINE, State.CLOSED);
456  }
457
458  public Map<ServerName, List<RegionInfo>> getSnapShotOfAssignment(
459      final Collection<RegionInfo> regions) {
460    final Map<ServerName, List<RegionInfo>> result = new HashMap<ServerName, List<RegionInfo>>();
461    if (regions != null) {
462      for (RegionInfo hri : regions) {
463        final RegionStateNode node = getRegionStateNode(hri);
464        if (node == null) {
465          continue;
466        }
467        createSnapshot(node, result);
468      }
469    } else {
470      for (RegionStateNode node : regionsMap.values()) {
471        if (node == null) {
472          continue;
473        }
474        createSnapshot(node, result);
475      }
476    }
477    return result;
478  }
479
480  private void createSnapshot(RegionStateNode node, Map<ServerName, List<RegionInfo>> result) {
481    final ServerName serverName = node.getRegionLocation();
482    if (serverName == null) {
483      return;
484    }
485
486    List<RegionInfo> serverRegions = result.get(serverName);
487    if (serverRegions == null) {
488      serverRegions = new ArrayList<RegionInfo>();
489      result.put(serverName, serverRegions);
490    }
491    serverRegions.add(node.getRegionInfo());
492  }
493
494  public Map<RegionInfo, ServerName> getRegionAssignments() {
495    final HashMap<RegionInfo, ServerName> assignments = new HashMap<RegionInfo, ServerName>();
496    for (RegionStateNode node: regionsMap.values()) {
497      assignments.put(node.getRegionInfo(), node.getRegionLocation());
498    }
499    return assignments;
500  }
501
502  public Map<RegionState.State, List<RegionInfo>> getRegionByStateOfTable(TableName tableName) {
503    final State[] states = State.values();
504    final Map<RegionState.State, List<RegionInfo>> tableRegions =
505        new HashMap<State, List<RegionInfo>>(states.length);
506    for (int i = 0; i < states.length; ++i) {
507      tableRegions.put(states[i], new ArrayList<RegionInfo>());
508    }
509
510    for (RegionStateNode node: regionsMap.values()) {
511      if (node.getTable().equals(tableName)) {
512        tableRegions.get(node.getState()).add(node.getRegionInfo());
513      }
514    }
515    return tableRegions;
516  }
517
518  public ServerName getRegionServerOfRegion(RegionInfo regionInfo) {
519    RegionStateNode regionNode = getRegionStateNode(regionInfo);
520    if (regionNode != null) {
521      regionNode.lock();
522      try {
523        ServerName server = regionNode.getRegionLocation();
524        return server != null ? server : regionNode.getLastHost();
525      } finally {
526        regionNode.unlock();
527      }
528    }
529    return null;
530  }
531
532  /**
533   * This is an EXPENSIVE clone.  Cloning though is the safest thing to do.
534   * Can't let out original since it can change and at least the load balancer
535   * wants to iterate this exported list.  We need to synchronize on regions
536   * since all access to this.servers is under a lock on this.regions.
537   *
538   * @param isByTable If <code>true</code>, return the assignments by table. If <code>false</code>,
539   *                  return the assignments which aggregate the server-load to the cluster level.
540   * @return A clone of current assignments.
541   */
542  public Map<TableName, Map<ServerName, List<RegionInfo>>> getAssignmentsForBalancer(
543    TableStateManager tableStateManager, List<ServerName> onlineServers, boolean isByTable) {
544    final Map<TableName, Map<ServerName, List<RegionInfo>>> result = new HashMap<>();
545    if (isByTable) {
546      for (RegionStateNode node : regionsMap.values()) {
547        if (isTableDisabled(tableStateManager, node.getTable())) {
548          continue;
549        }
550        Map<ServerName, List<RegionInfo>> tableResult =
551            result.computeIfAbsent(node.getTable(), t -> new HashMap<>());
552        final ServerName serverName = node.getRegionLocation();
553        if (serverName == null) {
554          LOG.info("Skipping, no server for " + node);
555          continue;
556        }
557        List<RegionInfo> serverResult =
558            tableResult.computeIfAbsent(serverName, s -> new ArrayList<>());
559        serverResult.add(node.getRegionInfo());
560      }
561      // Add online servers with no assignment for the table.
562      for (Map<ServerName, List<RegionInfo>> table : result.values()) {
563        for (ServerName serverName : onlineServers) {
564          table.putIfAbsent(serverName, new ArrayList<>());
565        }
566      }
567    } else {
568      final HashMap<ServerName, List<RegionInfo>> ensemble = new HashMap<>(serverMap.size());
569      for (ServerName serverName : onlineServers) {
570        ServerStateNode serverNode = serverMap.get(serverName);
571        if (serverNode != null) {
572          ensemble.put(serverNode.getServerName(), serverNode.getRegionInfoList().stream()
573            .filter(region -> !isTableDisabled(tableStateManager, region.getTable()))
574            .collect(Collectors.toList()));
575        } else {
576          ensemble.put(serverName, new ArrayList<>());
577        }
578      }
579      // Use a fake table name to represent the whole cluster's assignments
580      result.put(HConstants.ENSEMBLE_TABLE_NAME, ensemble);
581    }
582    return result;
583  }
584
585  private boolean isTableDisabled(final TableStateManager tableStateManager,
586    final TableName tableName) {
587    return tableStateManager
588      .isTableState(tableName, TableState.State.DISABLED, TableState.State.DISABLING);
589  }
590
591  // ==========================================================================
592  //  Region in transition helpers
593  // ==========================================================================
594  public boolean hasRegionsInTransition() {
595    return !regionInTransition.isEmpty();
596  }
597
598  public boolean isRegionInTransition(final RegionInfo regionInfo) {
599    final RegionStateNode node = regionInTransition.get(regionInfo);
600    return node != null ? node.isInTransition() : false;
601  }
602
603  public RegionState getRegionTransitionState(RegionInfo hri) {
604    RegionStateNode node = regionInTransition.get(hri);
605    if (node == null) {
606      return null;
607    }
608
609    node.lock();
610    try {
611      return node.isInTransition() ? node.toRegionState() : null;
612    } finally {
613      node.unlock();
614    }
615  }
616
617  public List<RegionStateNode> getRegionsInTransition() {
618    return new ArrayList<RegionStateNode>(regionInTransition.values());
619  }
620
621  /**
622   * Get the number of regions in transition.
623   */
624  public int getRegionsInTransitionCount() {
625    return regionInTransition.size();
626  }
627
628  public List<RegionState> getRegionsStateInTransition() {
629    final List<RegionState> rit = new ArrayList<RegionState>(regionInTransition.size());
630    for (RegionStateNode node: regionInTransition.values()) {
631      rit.add(node.toRegionState());
632    }
633    return rit;
634  }
635
636  public SortedSet<RegionState> getRegionsInTransitionOrderedByTimestamp() {
637    final SortedSet<RegionState> rit = new TreeSet<RegionState>(REGION_STATE_STAMP_COMPARATOR);
638    for (RegionStateNode node: regionInTransition.values()) {
639      rit.add(node.toRegionState());
640    }
641    return rit;
642  }
643
644  // ==========================================================================
645  //  Region offline helpers
646  // ==========================================================================
647  // TODO: Populated when we read meta but regions never make it out of here.
648  public void addToOfflineRegions(final RegionStateNode regionNode) {
649    LOG.info("Added to offline, CURRENTLY NEVER CLEARED!!! " + regionNode);
650    regionOffline.put(regionNode.getRegionInfo(), regionNode);
651  }
652
653  // TODO: Unused.
654  public void removeFromOfflineRegions(final RegionInfo regionInfo) {
655    regionOffline.remove(regionInfo);
656  }
657
658  // ==========================================================================
659  //  Region FAIL_OPEN helpers
660  // ==========================================================================
661  public static final class RegionFailedOpen {
662    private final RegionStateNode regionNode;
663
664    private volatile Exception exception = null;
665    private AtomicInteger retries = new AtomicInteger();
666
667    public RegionFailedOpen(final RegionStateNode regionNode) {
668      this.regionNode = regionNode;
669    }
670
671    public RegionStateNode getRegionStateNode() {
672      return regionNode;
673    }
674
675    public RegionInfo getRegionInfo() {
676      return regionNode.getRegionInfo();
677    }
678
679    public int incrementAndGetRetries() {
680      return this.retries.incrementAndGet();
681    }
682
683    public int getRetries() {
684      return retries.get();
685    }
686
687    public void setException(final Exception exception) {
688      this.exception = exception;
689    }
690
691    public Exception getException() {
692      return this.exception;
693    }
694  }
695
696  public RegionFailedOpen addToFailedOpen(final RegionStateNode regionNode) {
697    final byte[] key = regionNode.getRegionInfo().getRegionName();
698    RegionFailedOpen node = regionFailedOpen.get(key);
699    if (node == null) {
700      RegionFailedOpen newNode = new RegionFailedOpen(regionNode);
701      RegionFailedOpen oldNode = regionFailedOpen.putIfAbsent(key, newNode);
702      node = oldNode != null ? oldNode : newNode;
703    }
704    return node;
705  }
706
707  public RegionFailedOpen getFailedOpen(final RegionInfo regionInfo) {
708    return regionFailedOpen.get(regionInfo.getRegionName());
709  }
710
711  public void removeFromFailedOpen(final RegionInfo regionInfo) {
712    regionFailedOpen.remove(regionInfo.getRegionName());
713  }
714
715  public List<RegionState> getRegionFailedOpen() {
716    if (regionFailedOpen.isEmpty()) return Collections.emptyList();
717
718    ArrayList<RegionState> regions = new ArrayList<RegionState>(regionFailedOpen.size());
719    for (RegionFailedOpen r: regionFailedOpen.values()) {
720      regions.add(r.getRegionStateNode().toRegionState());
721    }
722    return regions;
723  }
724
725  // ==========================================================================
726  //  Servers
727  // ==========================================================================
728
729  /**
730   * Be judicious calling this method. Do it on server register ONLY otherwise
731   * you could mess up online server accounting. TOOD: Review usage and convert
732   * to {@link #getServerNode(ServerName)} where we can.
733   */
734  public ServerStateNode getOrCreateServer(final ServerName serverName) {
735    ServerStateNode node = serverMap.get(serverName);
736    if (node == null) {
737      node = new ServerStateNode(serverName);
738      ServerStateNode oldNode = serverMap.putIfAbsent(serverName, node);
739      node = oldNode != null ? oldNode : node;
740    }
741    return node;
742  }
743
744  public void removeServer(final ServerName serverName) {
745    serverMap.remove(serverName);
746  }
747
748  /**
749   * @return Pertinent ServerStateNode or NULL if none found.
750   */
751  @VisibleForTesting
752  public ServerStateNode getServerNode(final ServerName serverName) {
753    return serverMap.get(serverName);
754  }
755
756  public double getAverageLoad() {
757    int numServers = 0;
758    int totalLoad = 0;
759    for (ServerStateNode node: serverMap.values()) {
760      totalLoad += node.getRegionCount();
761      numServers++;
762    }
763    return numServers == 0 ? 0.0: (double)totalLoad / (double)numServers;
764  }
765
766  public ServerStateNode addRegionToServer(final RegionStateNode regionNode) {
767    ServerStateNode serverNode = getOrCreateServer(regionNode.getRegionLocation());
768    serverNode.addRegion(regionNode);
769    return serverNode;
770  }
771
772  public ServerStateNode removeRegionFromServer(final ServerName serverName,
773      final RegionStateNode regionNode) {
774    ServerStateNode serverNode = getOrCreateServer(serverName);
775    serverNode.removeRegion(regionNode);
776    return serverNode;
777  }
778
779  // ==========================================================================
780  //  ToString helpers
781  // ==========================================================================
782  public static String regionNamesToString(final Collection<byte[]> regions) {
783    final StringBuilder sb = new StringBuilder();
784    final Iterator<byte[]> it = regions.iterator();
785    sb.append("[");
786    if (it.hasNext()) {
787      sb.append(Bytes.toStringBinary(it.next()));
788      while (it.hasNext()) {
789        sb.append(", ");
790        sb.append(Bytes.toStringBinary(it.next()));
791      }
792    }
793    sb.append("]");
794    return sb.toString();
795  }
796}