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