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