View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.master;
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.TreeMap;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.MetaTableAccessor;
39  import org.apache.hadoop.hbase.RegionTransition;
40  import org.apache.hadoop.hbase.Server;
41  import org.apache.hadoop.hbase.ServerLoad;
42  import org.apache.hadoop.hbase.ServerName;
43  import org.apache.hadoop.hbase.TableName;
44  import org.apache.hadoop.hbase.TableStateManager;
45  import org.apache.hadoop.hbase.classification.InterfaceAudience;
46  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
47  import org.apache.hadoop.hbase.master.RegionState.State;
48  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
49  import org.apache.hadoop.hbase.util.Bytes;
50  import org.apache.hadoop.hbase.util.FSUtils;
51  import org.apache.hadoop.hbase.util.Pair;
52  import org.apache.hadoop.hbase.zookeeper.ZKAssign;
53  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
54  import org.apache.zookeeper.KeeperException;
55  
56  import com.google.common.annotations.VisibleForTesting;
57  import com.google.common.base.Preconditions;
58  
59  /**
60   * Region state accountant. It holds the states of all regions in the memory.
61   * In normal scenario, it should match the meta table and the true region states.
62   *
63   * This map is used by AssignmentManager to track region states.
64   */
65  @InterfaceAudience.Private
66  public class RegionStates {
67    private static final Log LOG = LogFactory.getLog(RegionStates.class);
68  
69    /**
70     * Regions currently in transition.
71     */
72    final HashMap<String, RegionState> regionsInTransition =
73      new HashMap<String, RegionState>();
74  
75    /**
76     * Region encoded name to state map.
77     * All the regions should be in this map.
78     */
79    private final Map<String, RegionState> regionStates =
80      new HashMap<String, RegionState>();
81  
82    /**
83     * Holds mapping of table -> region state
84     */
85    private final Map<TableName, Map<String, RegionState>> regionStatesTableIndex =
86        new HashMap<TableName, Map<String, RegionState>>();
87  
88    /**
89     * Server to regions assignment map.
90     * Contains the set of regions currently assigned to a given server.
91     */
92    private final Map<ServerName, Set<HRegionInfo>> serverHoldings =
93      new HashMap<ServerName, Set<HRegionInfo>>();
94  
95    /**
96     * Maintains the mapping from the default region to the replica regions.
97     */
98    private final Map<HRegionInfo, Set<HRegionInfo>> defaultReplicaToOtherReplicas =
99      new HashMap<HRegionInfo, Set<HRegionInfo>>();
100 
101   /**
102    * Region to server assignment map.
103    * Contains the server a given region is currently assigned to.
104    */
105   private final TreeMap<HRegionInfo, ServerName> regionAssignments =
106     new TreeMap<HRegionInfo, ServerName>();
107 
108   /**
109    * Encoded region name to server assignment map for re-assignment
110    * purpose. Contains the server a given region is last known assigned
111    * to, which has not completed log splitting, so not assignable.
112    * If a region is currently assigned, this server info in this
113    * map should be the same as that in regionAssignments.
114    * However the info in regionAssignments is cleared when the region
115    * is offline while the info in lastAssignments is cleared when
116    * the region is closed or the server is dead and processed.
117    */
118   private final HashMap<String, ServerName> lastAssignments =
119     new HashMap<String, ServerName>();
120 
121   /**
122    * Encoded region name to server assignment map for the
123    * purpose to clean up serverHoldings when a region is online
124    * on a new server. When the region is offline from the previous
125    * server, we cleaned up regionAssignments so that it has the
126    * latest assignment map. But we didn't clean up serverHoldings
127    * to match the meta. We need this map to find out the old server
128    * whose serverHoldings needs cleanup, given a moved region.
129    */
130   private final HashMap<String, ServerName> oldAssignments =
131     new HashMap<String, ServerName>();
132 
133   /**
134    * Map a host port pair string to the latest start code
135    * of a region server which is known to be dead. It is dead
136    * to us, but server manager may not know it yet.
137    */
138   private final HashMap<String, Long> deadServers =
139     new HashMap<String, Long>();
140 
141   /**
142    * Map a dead servers to the time when log split is done.
143    * Since log splitting is not ordered, we have to remember
144    * all processed instances. The map is cleaned up based
145    * on a configured time. By default, we assume a dead
146    * server should be done with log splitting in two hours.
147    */
148   private final HashMap<ServerName, Long> processedServers =
149     new HashMap<ServerName, Long>();
150   private long lastProcessedServerCleanTime;
151 
152   private final TableStateManager tableStateManager;
153   private final RegionStateStore regionStateStore;
154   private final ServerManager serverManager;
155   private final Server server;
156 
157   // The maximum time to keep a log split info in region states map
158   static final String LOG_SPLIT_TIME = "hbase.master.maximum.logsplit.keeptime";
159   static final long DEFAULT_LOG_SPLIT_TIME = 7200000L; // 2 hours
160 
161   RegionStates(final Server master, final TableStateManager tableStateManager,
162       final ServerManager serverManager, final RegionStateStore regionStateStore) {
163     this.tableStateManager = tableStateManager;
164     this.regionStateStore = regionStateStore;
165     this.serverManager = serverManager;
166     this.server = master;
167   }
168 
169   /**
170    * @return a copy of the region assignment map
171    */
172   public synchronized Map<HRegionInfo, ServerName> getRegionAssignments() {
173     return new TreeMap<HRegionInfo, ServerName>(regionAssignments);
174   }
175 
176   /**
177    * Return the replicas (including default) for the regions grouped by ServerName
178    * @param regions
179    * @return a pair containing the groupings as a map
180    */
181   synchronized Map<ServerName, List<HRegionInfo>> getRegionAssignments(
182     Collection<HRegionInfo> regions) {
183     Map<ServerName, List<HRegionInfo>> map = new HashMap<ServerName, List<HRegionInfo>>();
184     for (HRegionInfo region : regions) {
185       HRegionInfo defaultReplica = RegionReplicaUtil.getRegionInfoForDefaultReplica(region);
186       Set<HRegionInfo> allReplicas = defaultReplicaToOtherReplicas.get(defaultReplica);
187       if (allReplicas != null) {
188         for (HRegionInfo hri : allReplicas) {
189           ServerName server = regionAssignments.get(hri);
190           if (server != null) {
191             List<HRegionInfo> regionsOnServer = map.get(server);
192             if (regionsOnServer == null) {
193               regionsOnServer = new ArrayList<HRegionInfo>(1);
194               map.put(server, regionsOnServer);
195             }
196             regionsOnServer.add(hri);
197           }
198         }
199       }
200     }
201     return map;
202   }
203 
204   public synchronized ServerName getRegionServerOfRegion(HRegionInfo hri) {
205     return regionAssignments.get(hri);
206   }
207 
208   /**
209    * Get regions in transition and their states
210    */
211   @SuppressWarnings("unchecked")
212   public synchronized Map<String, RegionState> getRegionsInTransition() {
213     return (Map<String, RegionState>)regionsInTransition.clone();
214   }
215 
216   /**
217    * @return True if specified region in transition.
218    */
219   public synchronized boolean isRegionInTransition(final HRegionInfo hri) {
220     return regionsInTransition.containsKey(hri.getEncodedName());
221   }
222 
223   /**
224    * @return True if specified region in transition.
225    */
226   public synchronized boolean isRegionInTransition(final String encodedName) {
227     return regionsInTransition.containsKey(encodedName);
228   }
229 
230   /**
231    * @return True if any region in transition.
232    */
233   public synchronized boolean isRegionsInTransition() {
234     return !regionsInTransition.isEmpty();
235   }
236 
237   /**
238    * @return True if hbase:meta table region is in transition.
239    */
240   public synchronized boolean isMetaRegionInTransition() {
241     for (RegionState state : regionsInTransition.values()) {
242       if (state.getRegion().isMetaRegion()) return true;
243     }
244     return false;
245   }
246 
247   /**
248    * @return True if specified region assigned, and not in transition.
249    */
250   public synchronized boolean isRegionOnline(final HRegionInfo hri) {
251     return !isRegionInTransition(hri) && regionAssignments.containsKey(hri);
252   }
253 
254   /**
255    * @return True if specified region offline/closed, but not in transition.
256    * If the region is not in the map, it is offline to us too.
257    */
258   public synchronized boolean isRegionOffline(final HRegionInfo hri) {
259     return getRegionState(hri) == null || (!isRegionInTransition(hri)
260       && isRegionInState(hri, State.OFFLINE, State.CLOSED));
261   }
262 
263   /**
264    * @return True if specified region is in one of the specified states.
265    */
266   public boolean isRegionInState(
267       final HRegionInfo hri, final State... states) {
268     return isRegionInState(hri.getEncodedName(), states);
269   }
270 
271   /**
272    * @return True if specified region is in one of the specified states.
273    */
274   public boolean isRegionInState(
275       final String encodedName, final State... states) {
276     RegionState regionState = getRegionState(encodedName);
277     return isOneOfStates(regionState, states);
278   }
279 
280   /**
281    * Wait for the state map to be updated by assignment manager.
282    */
283   public synchronized void waitForUpdate(
284       final long timeout) throws InterruptedException {
285     this.wait(timeout);
286   }
287 
288   /**
289    * Get region transition state
290    */
291   public RegionState getRegionTransitionState(final HRegionInfo hri) {
292     return getRegionTransitionState(hri.getEncodedName());
293   }
294 
295   /**
296    * Get region transition state
297    */
298   public synchronized RegionState
299       getRegionTransitionState(final String encodedName) {
300     return regionsInTransition.get(encodedName);
301   }
302 
303   /**
304    * Add a list of regions to RegionStates. If a region is split
305    * and offline, its state will be SPLIT. Otherwise, its state will
306    * be OFFLINE. Region already in RegionStates will be skipped.
307    */
308   public void createRegionStates(
309       final List<HRegionInfo> hris) {
310     for (HRegionInfo hri: hris) {
311       createRegionState(hri);
312     }
313   }
314 
315   /**
316    * Add a region to RegionStates. If the region is split
317    * and offline, its state will be SPLIT. Otherwise, its state will
318    * be OFFLINE. If it is already in RegionStates, this call has
319    * no effect, and the original state is returned.
320    */
321   public RegionState createRegionState(final HRegionInfo hri) {
322     return createRegionState(hri, null, null, null);
323   }
324 
325   /**
326    * Add a region to RegionStates with the specified state.
327    * If the region is already in RegionStates, this call has
328    * no effect, and the original state is returned.
329    *
330    * @param hri the region info to create a state for
331    * @param newState the state to the region in set to
332    * @param serverName the server the region is transitioning on
333    * @param lastHost the last server that hosts the region
334    * @return the current state
335    */
336   public synchronized RegionState createRegionState(final HRegionInfo hri,
337       State newState, ServerName serverName, ServerName lastHost) {
338     if (newState == null || (newState == State.OPEN && serverName == null)) {
339       newState =  State.OFFLINE;
340     }
341     if (hri.isOffline() && hri.isSplit()) {
342       newState = State.SPLIT;
343       serverName = null;
344     }
345     String encodedName = hri.getEncodedName();
346     RegionState regionState = regionStates.get(encodedName);
347     if (regionState != null) {
348       LOG.warn("Tried to create a state for a region already in RegionStates, "
349         + "used existing: " + regionState + ", ignored new: " + newState);
350     } else {
351       regionState = new RegionState(hri, newState, serverName);
352       putRegionState(regionState);
353       if (newState == State.OPEN) {
354         if (!serverName.equals(lastHost)) {
355           LOG.warn("Open region's last host " + lastHost
356             + " should be the same as the current one " + serverName
357             + ", ignored the last and used the current one");
358           lastHost = serverName;
359         }
360         lastAssignments.put(encodedName, lastHost);
361         regionAssignments.put(hri, lastHost);
362       } else if (!regionState.isUnassignable()) {
363         regionsInTransition.put(encodedName, regionState);
364       }
365       if (lastHost != null && newState != State.SPLIT) {
366         addToServerHoldings(lastHost, hri);
367         if (newState != State.OPEN) {
368           oldAssignments.put(encodedName, lastHost);
369         }
370       }
371     }
372     return regionState;
373   }
374 
375   private RegionState putRegionState(RegionState regionState) {
376     HRegionInfo hri = regionState.getRegion();
377     String encodedName = hri.getEncodedName();
378     TableName table = hri.getTable();
379     RegionState oldState = regionStates.put(encodedName, regionState);
380     Map<String, RegionState> map = regionStatesTableIndex.get(table);
381     if (map == null) {
382       map = new HashMap<String, RegionState>();
383       regionStatesTableIndex.put(table, map);
384     }
385     map.put(encodedName, regionState);
386     return oldState;
387   }
388 
389   /**
390    * Set the region state to CLOSED
391    */
392   public RegionState setRegionStateTOCLOSED(
393       final byte[] regionName,
394       final ServerName serverName) {
395     HRegionInfo regionInfo = getRegionInfo(regionName);
396     return setRegionStateTOCLOSED(regionInfo, serverName);
397   }
398 
399   /**
400    * Set the region state to CLOSED
401    */
402   public RegionState setRegionStateTOCLOSED(
403       final HRegionInfo regionInfo,
404       final ServerName serverName) {
405     ServerName sn = serverName;
406     if (sn == null) {
407       RegionState regionState = getRegionState(regionInfo.getEncodedName());
408       if (regionState != null) {
409         sn = regionState.getServerName();
410       }
411       // TODO: if sn is null, should we dig into
412       // lastAssignments.get(regionInfo.getEncodedName() to get the server name?
413       // For now, I just keep the same logic that works in the past
414     }
415     // We have to make sure that the last region server is set to be the same as the
416     // current RS.  If we don't do that, we could run into situation that both AM and SSH
417     // think other would do the assignment work; at the end, neither does the work and
418     // region remains RIT.
419     // See HBASE-13330 and HBASE-17023
420     setLastRegionServerOfRegion(sn, regionInfo.getEncodedName());
421     return updateRegionState(regionInfo, State.CLOSED, sn);
422   }
423 
424   /**
425    * Update a region state. It will be put in transition if not already there.
426    */
427   public RegionState updateRegionState(
428       final HRegionInfo hri, final State state) {
429     RegionState regionState = getRegionState(hri.getEncodedName());
430     return updateRegionState(hri, state,
431       regionState == null ? null : regionState.getServerName());
432   }
433 
434   /**
435    * Update a region state. It will be put in transition if not already there.
436    *
437    * If we can't find the region info based on the region name in
438    * the transition, log a warning and return null.
439    */
440   public RegionState updateRegionState(
441       final RegionTransition transition, final State state) {
442     byte [] regionName = transition.getRegionName();
443     HRegionInfo regionInfo = getRegionInfo(regionName);
444     if (regionInfo == null) {
445       String prettyRegionName = HRegionInfo.prettyPrint(
446         HRegionInfo.encodeRegionName(regionName));
447       LOG.warn("Failed to find region " + prettyRegionName
448         + " in updating its state to " + state
449         + " based on region transition " + transition);
450       return null;
451     }
452     return updateRegionState(regionInfo, state,
453       transition.getServerName());
454   }
455 
456   /**
457    * Transition a region state to OPEN from OPENING/PENDING_OPEN
458    */
459   public synchronized RegionState transitionOpenFromPendingOpenOrOpeningOnServer(
460       final RegionTransition transition, final RegionState fromState, final ServerName sn) {
461     if(fromState.isPendingOpenOrOpeningOnServer(sn)){
462       return updateRegionState(transition, State.OPEN);
463     }
464     return null;
465   }
466 
467   /**
468    * Update a region state. It will be put in transition if not already there.
469    */
470   public RegionState updateRegionState(
471       final HRegionInfo hri, final State state, final ServerName serverName) {
472     return updateRegionState(hri, state, serverName, HConstants.NO_SEQNUM);
473   }
474 
475   public void regionOnline(final HRegionInfo hri, final ServerName serverName) {
476     regionOnline(hri, serverName, HConstants.NO_SEQNUM);
477   }
478 
479   /**
480    * A region is online, won't be in transition any more.
481    * We can't confirm it is really online on specified region server
482    * because it hasn't been put in region server's online region list yet.
483    */
484   public void regionOnline(final HRegionInfo hri, final ServerName serverName, long openSeqNum) {
485     String encodedName = hri.getEncodedName();
486     if (!serverManager.isServerOnline(serverName)) {
487       // This is possible if the region server dies before master gets a
488       // chance to handle ZK event in time. At this time, if the dead server
489       // is already processed by SSH, we should ignore this event.
490       // If not processed yet, ignore and let SSH deal with it.
491       LOG.warn("Ignored, " + encodedName + " was opened on a dead server: " + serverName);
492       return;
493     }
494     updateRegionState(hri, State.OPEN, serverName, openSeqNum);
495 
496     synchronized (this) {
497       regionsInTransition.remove(encodedName);
498       ServerName oldServerName = regionAssignments.put(hri, serverName);
499       if (!serverName.equals(oldServerName)) {
500         if (LOG.isDebugEnabled()) {
501           LOG.debug("Onlined " + hri.getShortNameToLog() + " on " + serverName);
502         }
503         addToServerHoldings(serverName, hri);
504         addToReplicaMapping(hri);
505         if (oldServerName == null) {
506           oldServerName = oldAssignments.remove(encodedName);
507         }
508         if (oldServerName != null
509             && !oldServerName.equals(serverName)
510             && serverHoldings.containsKey(oldServerName)) {
511           LOG.info("Offlined " + hri.getShortNameToLog() + " from " + oldServerName);
512           removeFromServerHoldings(oldServerName, hri);
513         }
514       }
515     }
516   }
517 
518   private void addToServerHoldings(ServerName serverName, HRegionInfo hri) {
519     Set<HRegionInfo> regions = serverHoldings.get(serverName);
520     if (regions == null) {
521       regions = new HashSet<HRegionInfo>();
522       serverHoldings.put(serverName, regions);
523     }
524     regions.add(hri);
525   }
526 
527   private void addToReplicaMapping(HRegionInfo hri) {
528     HRegionInfo defaultReplica = RegionReplicaUtil.getRegionInfoForDefaultReplica(hri);
529     Set<HRegionInfo> replicas =
530         defaultReplicaToOtherReplicas.get(defaultReplica);
531     if (replicas == null) {
532       replicas = new HashSet<HRegionInfo>();
533       defaultReplicaToOtherReplicas.put(defaultReplica, replicas);
534     }
535     replicas.add(hri);
536   }
537 
538   private void removeFromServerHoldings(ServerName serverName, HRegionInfo hri) {
539     Set<HRegionInfo> oldRegions = serverHoldings.get(serverName);
540     oldRegions.remove(hri);
541     if (oldRegions.isEmpty()) {
542       serverHoldings.remove(serverName);
543     }
544   }
545 
546   private void removeFromReplicaMapping(HRegionInfo hri) {
547     HRegionInfo defaultReplica = RegionReplicaUtil.getRegionInfoForDefaultReplica(hri);
548     Set<HRegionInfo> replicas = defaultReplicaToOtherReplicas.get(defaultReplica);
549     if (replicas != null) {
550       replicas.remove(hri);
551       if (replicas.isEmpty()) {
552         defaultReplicaToOtherReplicas.remove(defaultReplica);
553       }
554     }
555   }
556 
557   /**
558    * A dead server's wals have been split so that all the regions
559    * used to be open on it can be safely assigned now. Mark them assignable.
560    */
561   public synchronized void logSplit(final ServerName serverName) {
562     for (Iterator<Map.Entry<String, ServerName>> it
563         = lastAssignments.entrySet().iterator(); it.hasNext();) {
564       Map.Entry<String, ServerName> e = it.next();
565       if (e.getValue().equals(serverName)) {
566         it.remove();
567       }
568     }
569     long now = System.currentTimeMillis();
570     if (LOG.isDebugEnabled()) {
571       LOG.debug("Adding to log splitting servers " + serverName);
572     }
573     processedServers.put(serverName, Long.valueOf(now));
574     Configuration conf = server.getConfiguration();
575     long obsoleteTime = conf.getLong(LOG_SPLIT_TIME, DEFAULT_LOG_SPLIT_TIME);
576     // Doesn't have to be very accurate about the clean up time
577     if (now > lastProcessedServerCleanTime + obsoleteTime) {
578       lastProcessedServerCleanTime = now;
579       long cutoff = now - obsoleteTime;
580       for (Iterator<Map.Entry<ServerName, Long>> it
581           = processedServers.entrySet().iterator(); it.hasNext();) {
582         Map.Entry<ServerName, Long> e = it.next();
583         if (e.getValue().longValue() < cutoff) {
584           if (LOG.isDebugEnabled()) {
585             LOG.debug("Removed from log splitting servers " + e.getKey());
586           }
587           it.remove();
588         }
589       }
590     }
591   }
592 
593   /**
594    * Log split is done for a given region, so it is assignable now.
595    */
596   public void logSplit(final HRegionInfo region) {
597     clearLastAssignment(region);
598   }
599 
600   public synchronized void clearLastAssignment(final HRegionInfo region) {
601     lastAssignments.remove(region.getEncodedName());
602   }
603 
604   /**
605    * A region is offline, won't be in transition any more.
606    */
607   public void regionOffline(final HRegionInfo hri) {
608     regionOffline(hri, null);
609   }
610 
611   /**
612    * A region is offline, won't be in transition any more. Its state
613    * should be the specified expected state, which can only be
614    * Split/Merged/Offline/null(=Offline)/SplittingNew/MergingNew.
615    */
616   public void regionOffline(
617       final HRegionInfo hri, final State expectedState) {
618     Preconditions.checkArgument(expectedState == null
619       || RegionState.isUnassignable(expectedState),
620         "Offlined region should not be " + expectedState);
621     if (isRegionInState(hri, State.SPLITTING_NEW, State.MERGING_NEW)) {
622       // Remove it from all region maps
623       deleteRegion(hri);
624       return;
625     }
626     State newState =
627       expectedState == null ? State.OFFLINE : expectedState;
628     updateRegionState(hri, newState);
629     String encodedName = hri.getEncodedName();
630     synchronized (this) {
631       regionsInTransition.remove(encodedName);
632       ServerName oldServerName = regionAssignments.remove(hri);
633       if (oldServerName != null && serverHoldings.containsKey(oldServerName)) {
634         if (newState == State.MERGED || newState == State.SPLIT
635             || hri.isMetaRegion() || tableStateManager.isTableState(hri.getTable(),
636               ZooKeeperProtos.Table.State.DISABLED, ZooKeeperProtos.Table.State.DISABLING)) {
637           // Offline the region only if it's merged/split, or the table is disabled/disabling.
638           // Otherwise, offline it from this server only when it is online on a different server.
639           LOG.info("Offlined " + hri.getShortNameToLog() + " from " + oldServerName);
640           removeFromServerHoldings(oldServerName, hri);
641           removeFromReplicaMapping(hri);
642         } else {
643           // Need to remember it so that we can offline it from this
644           // server when it is online on a different server.
645           oldAssignments.put(encodedName, oldServerName);
646         }
647       }
648     }
649   }
650 
651   /**
652    * A server is offline, all regions on it are dead.
653    */
654   public List<HRegionInfo> serverOffline(final ZooKeeperWatcher watcher, final ServerName sn) {
655     // Offline all regions on this server not already in transition.
656     List<HRegionInfo> rits = new ArrayList<HRegionInfo>();
657     Set<HRegionInfo> regionsToCleanIfNoMetaEntry = new HashSet<HRegionInfo>();
658     // Offline regions outside the loop and synchronized block to avoid
659     // ConcurrentModificationException and deadlock in case of meta anassigned,
660     // but RegionState a blocked.
661     Set<HRegionInfo> regionsToOffline = new HashSet<HRegionInfo>();
662     synchronized (this) {
663       Set<HRegionInfo> assignedRegions = serverHoldings.get(sn);
664       if (assignedRegions == null) {
665         assignedRegions = new HashSet<HRegionInfo>();
666       }
667 
668       for (HRegionInfo region : assignedRegions) {
669         // Offline open regions, no need to offline if SPLIT/MERGED/OFFLINE
670         if (isRegionOnline(region)) {
671           regionsToOffline.add(region);
672         } else if (isRegionInState(region, State.SPLITTING, State.MERGING)) {
673           LOG.debug("Offline splitting/merging region " + getRegionState(region));
674           try {
675             // Delete the ZNode if exists
676             ZKAssign.deleteNodeFailSilent(watcher, region);
677             regionsToOffline.add(region);
678           } catch (KeeperException ke) {
679             server.abort("Unexpected ZK exception deleting node " + region, ke);
680           }
681         }
682       }
683 
684       for (RegionState state : regionsInTransition.values()) {
685         HRegionInfo hri = state.getRegion();
686         if (assignedRegions.contains(hri)) {
687           // Region is open on this region server, but in transition.
688           // This region must be moving away from this server, or splitting/merging.
689           // SSH will handle it, either skip assigning, or re-assign.
690           LOG.info("Transitioning " + state + " will be handled by ServerCrashProcedure for " + sn);
691         } else if (sn.equals(state.getServerName())) {
692           // Region is in transition on this region server, and this
693           // region is not open on this server. So the region must be
694           // moving to this server from another one (i.e. opening or
695           // pending open on this server, was open on another one.
696           // Offline state is also kind of pending open if the region is in
697           // transition. The region could be in failed_close state too if we have
698           // tried several times to open it while this region server is not reachable)
699           if (state.isPendingOpenOrOpening() || state.isFailedClose() || state.isOffline()) {
700             LOG.info("Found region in " + state +
701               " to be reassigned by ServerCrashProcedure for " + sn);
702             rits.add(hri);
703           } else if(state.isSplittingNew() || state.isMergingNew()) {
704             regionsToCleanIfNoMetaEntry.add(state.getRegion());
705           } else {
706             LOG.warn("THIS SHOULD NOT HAPPEN: unexpected " + state);
707           }
708         }
709       }
710       this.notifyAll();
711     }
712 
713     for (HRegionInfo hri : regionsToOffline) {
714       regionOffline(hri);
715     }
716 
717     cleanIfNoMetaEntry(regionsToCleanIfNoMetaEntry);
718     return rits;
719   }
720 
721   /**
722    * This method does an RPC to hbase:meta. Do not call this method with a lock/synchronize held.
723    * @param hris The hris to check if empty in hbase:meta and if so, clean them up.
724    */
725   private void cleanIfNoMetaEntry(Set<HRegionInfo> hris) {
726     if (hris.isEmpty()) return;
727     for (HRegionInfo hri: hris) {
728       try {
729         // This is RPC to meta table. It is done while we have a synchronize on
730         // regionstates. No progress will be made if meta is not available at this time.
731         // This is a cleanup task. Not critical.
732         if (MetaTableAccessor.getRegion(server.getConnection(), hri.getEncodedNameAsBytes()) ==
733             null) {
734           regionOffline(hri);
735           FSUtils.deleteRegionDir(server.getConfiguration(), hri);
736         }
737       } catch (IOException e) {
738         LOG.warn("Got exception while deleting " + hri + " directories from file system.", e);
739       }
740     }
741   }
742 
743   /**
744    * Gets the online regions of the specified table.
745    * This method looks at the in-memory state.  It does not go to <code>hbase:meta</code>.
746    * Only returns <em>online</em> regions.  If a region on this table has been
747    * closed during a disable, etc., it will be included in the returned list.
748    * So, the returned list may not necessarily be ALL regions in this table, its
749    * all the ONLINE regions in the table.
750    * @param tableName
751    * @return Online regions from <code>tableName</code>
752    */
753   public synchronized List<HRegionInfo> getRegionsOfTable(TableName tableName) {
754     List<HRegionInfo> tableRegions = new ArrayList<HRegionInfo>();
755     // boundary needs to have table's name but regionID 0 so that it is sorted
756     // before all table's regions.
757     HRegionInfo boundary = new HRegionInfo(tableName, null, null, false, 0L);
758     for (HRegionInfo hri: regionAssignments.tailMap(boundary).keySet()) {
759       if(!hri.getTable().equals(tableName)) break;
760       tableRegions.add(hri);
761     }
762     return tableRegions;
763   }
764 
765   /**
766    * Gets current state of all regions of the table.
767    * This method looks at the in-memory state.  It does not go to <code>hbase:meta</code>.
768    * Method guaranteed to return keys for all states
769    * in {@link org.apache.hadoop.hbase.master.RegionState.State}
770    *
771    * @param tableName
772    * @return Online regions from <code>tableName</code>
773    */
774   public synchronized Map<RegionState.State, List<HRegionInfo>>
775   getRegionByStateOfTable(TableName tableName) {
776     Map<RegionState.State, List<HRegionInfo>> tableRegions =
777         new HashMap<State, List<HRegionInfo>>();
778     for (State state : State.values()) {
779       tableRegions.put(state, new ArrayList<HRegionInfo>());
780     }
781     Map<String, RegionState> indexMap = regionStatesTableIndex.get(tableName);
782     if (indexMap == null)
783       return tableRegions;
784     for (RegionState regionState : indexMap.values()) {
785       tableRegions.get(regionState.getState()).add(regionState.getRegion());
786     }
787     return tableRegions;
788   }
789 
790   /**
791    * Wait on region to clear regions-in-transition.
792    * <p>
793    * If the region isn't in transition, returns immediately.  Otherwise, method
794    * blocks until the region is out of transition.
795    */
796   public synchronized void waitOnRegionToClearRegionsInTransition(
797       final HRegionInfo hri) throws InterruptedException {
798     if (!isRegionInTransition(hri)) return;
799 
800     while(!server.isStopped() && isRegionInTransition(hri)) {
801       RegionState rs = getRegionState(hri);
802       LOG.info("Waiting on " + rs + " to clear regions-in-transition");
803       waitForUpdate(100);
804     }
805 
806     if (server.isStopped()) {
807       LOG.info("Giving up wait on region in " +
808         "transition because stoppable.isStopped is set");
809     }
810   }
811 
812   /**
813    * A table is deleted. Remove its regions from all internal maps.
814    * We loop through all regions assuming we don't delete tables too much.
815    */
816   public void tableDeleted(final TableName tableName) {
817     Set<HRegionInfo> regionsToDelete = new HashSet<HRegionInfo>();
818     synchronized (this) {
819       for (RegionState state: regionStates.values()) {
820         HRegionInfo region = state.getRegion();
821         if (region.getTable().equals(tableName)) {
822           regionsToDelete.add(region);
823         }
824       }
825     }
826     for (HRegionInfo region: regionsToDelete) {
827       deleteRegion(region);
828     }
829   }
830 
831   /**
832    * Get a copy of all regions assigned to a server
833    */
834   public synchronized Set<HRegionInfo> getServerRegions(ServerName serverName) {
835     Set<HRegionInfo> regions = serverHoldings.get(serverName);
836     if (regions == null) return null;
837     return new HashSet<HRegionInfo>(regions);
838   }
839 
840   /**
841    * Remove a region from all state maps.
842    */
843   @VisibleForTesting
844   public synchronized void deleteRegion(final HRegionInfo hri) {
845     String encodedName = hri.getEncodedName();
846     regionsInTransition.remove(encodedName);
847     regionStates.remove(encodedName);
848     TableName table = hri.getTable();
849     Map<String, RegionState> indexMap = regionStatesTableIndex.get(table);
850     indexMap.remove(encodedName);
851     if (indexMap.size() == 0)
852       regionStatesTableIndex.remove(table);
853     lastAssignments.remove(encodedName);
854     ServerName sn = regionAssignments.remove(hri);
855     if (sn != null) {
856       Set<HRegionInfo> regions = serverHoldings.get(sn);
857       regions.remove(hri);
858     }
859   }
860 
861   /**
862    * Checking if a region was assigned to a server which is not online now.
863    * If so, we should hold re-assign this region till SSH has split its wals.
864    * Once logs are split, the last assignment of this region will be reset,
865    * which means a null last assignment server is ok for re-assigning.
866    *
867    * A region server could be dead but we don't know it yet. We may
868    * think it's online falsely. Therefore if a server is online, we still
869    * need to confirm it reachable and having the expected start code.
870    */
871   synchronized boolean wasRegionOnDeadServer(final String encodedName) {
872     ServerName server = lastAssignments.get(encodedName);
873     return isServerDeadAndNotProcessed(server);
874   }
875 
876   synchronized boolean isServerDeadAndNotProcessed(ServerName server) {
877     if (server == null) return false;
878     if (serverManager.isServerOnline(server)) {
879       String hostAndPort = server.getHostAndPort();
880       long startCode = server.getStartcode();
881       Long deadCode = deadServers.get(hostAndPort);
882       if (deadCode == null || startCode > deadCode.longValue()) {
883         if (serverManager.isServerReachable(server)) {
884           return false;
885         }
886         // The size of deadServers won't grow unbounded.
887         deadServers.put(hostAndPort, Long.valueOf(startCode));
888       }
889       // Watch out! If the server is not dead, the region could
890       // remain unassigned. That's why ServerManager#isServerReachable
891       // should use some retry.
892       //
893       // We cache this info since it is very unlikely for that
894       // instance to come back up later on. We don't want to expire
895       // the server since we prefer to let it die naturally.
896       LOG.warn("Couldn't reach online server " + server);
897     }
898     // Now, we know it's dead. Check if it's processed
899     return !processedServers.containsKey(server);
900   }
901 
902  /**
903    * Get the last region server a region was on for purpose of re-assignment,
904    * i.e. should the re-assignment be held back till log split is done?
905    */
906   synchronized ServerName getLastRegionServerOfRegion(final String encodedName) {
907     return lastAssignments.get(encodedName);
908   }
909 
910   synchronized void setLastRegionServerOfRegions(
911       final ServerName serverName, final List<HRegionInfo> regionInfos) {
912     for (HRegionInfo hri: regionInfos) {
913       setLastRegionServerOfRegion(serverName, hri.getEncodedName());
914     }
915   }
916 
917   synchronized void setLastRegionServerOfRegion(
918       final ServerName serverName, final String encodedName) {
919     lastAssignments.put(encodedName, serverName);
920   }
921 
922   void splitRegion(HRegionInfo p,
923       HRegionInfo a, HRegionInfo b, ServerName sn) throws IOException {
924 
925     regionStateStore.splitRegion(p, a, b, sn, getRegionReplication(p));
926     synchronized (this) {
927       // After PONR, split is considered to be done.
928       // Update server holdings to be aligned with the meta.
929       Set<HRegionInfo> regions = serverHoldings.get(sn);
930       if (regions == null) {
931         throw new IllegalStateException(sn + " should host some regions");
932       }
933       regions.remove(p);
934       regions.add(a);
935       regions.add(b);
936     }
937   }
938 
939   void mergeRegions(HRegionInfo p,
940       HRegionInfo a, HRegionInfo b, ServerName sn) throws IOException {
941     regionStateStore.mergeRegions(p, a, b, sn, getRegionReplication(a));
942     synchronized (this) {
943       // After PONR, merge is considered to be done.
944       // Update server holdings to be aligned with the meta.
945       Set<HRegionInfo> regions = serverHoldings.get(sn);
946       if (regions == null) {
947         throw new IllegalStateException(sn + " should host some regions");
948       }
949       regions.remove(a);
950       regions.remove(b);
951       regions.add(p);
952     }
953   }
954 
955   private int getRegionReplication(HRegionInfo r) throws IOException {
956     if (tableStateManager != null) {
957       HTableDescriptor htd = ((MasterServices)server).getTableDescriptors().get(r.getTable());
958       if (htd != null) {
959         return htd.getRegionReplication();
960       }
961     }
962     return 1;
963   }
964 
965   /**
966    * At cluster clean re/start, mark all user regions closed except those of tables
967    * that are excluded, such as disabled/disabling/enabling tables. All user regions
968    * and their previous locations are returned.
969    */
970   synchronized Map<HRegionInfo, ServerName> closeAllUserRegions(Set<TableName> excludedTables) {
971     boolean noExcludeTables = excludedTables == null || excludedTables.isEmpty();
972     Set<HRegionInfo> toBeClosed = new HashSet<HRegionInfo>(regionStates.size());
973     for(RegionState state: regionStates.values()) {
974       HRegionInfo hri = state.getRegion();
975       if (state.isSplit() || hri.isSplit()) {
976         continue;
977       }
978       TableName tableName = hri.getTable();
979       if (!TableName.META_TABLE_NAME.equals(tableName)
980           && (noExcludeTables || !excludedTables.contains(tableName))) {
981         toBeClosed.add(hri);
982       }
983     }
984     Map<HRegionInfo, ServerName> allUserRegions =
985       new HashMap<HRegionInfo, ServerName>(toBeClosed.size());
986     for (HRegionInfo hri: toBeClosed) {
987       RegionState regionState = updateRegionState(hri, State.CLOSED);
988       allUserRegions.put(hri, regionState.getServerName());
989     }
990     return allUserRegions;
991   }
992 
993   /**
994    * Compute the average load across all region servers.
995    * Currently, this uses a very naive computation - just uses the number of
996    * regions being served, ignoring stats about number of requests.
997    * @return the average load
998    */
999   protected synchronized double getAverageLoad() {
1000     int numServers = 0, totalLoad = 0;
1001     for (Map.Entry<ServerName, Set<HRegionInfo>> e: serverHoldings.entrySet()) {
1002       Set<HRegionInfo> regions = e.getValue();
1003       ServerName serverName = e.getKey();
1004       int regionCount = regions.size();
1005       if (serverManager.isServerOnline(serverName)) {
1006         totalLoad += regionCount;
1007         numServers++;
1008       }
1009     }
1010     if (numServers > 1) {
1011       // The master region server holds only a couple regions.
1012       // Don't consider this server in calculating the average load
1013       // if there are other region servers to avoid possible confusion.
1014       Set<HRegionInfo> hris = serverHoldings.get(server.getServerName());
1015       if (hris != null) {
1016         totalLoad -= hris.size();
1017         numServers--;
1018       }
1019     }
1020     return numServers == 0 ? 0.0 :
1021       (double)totalLoad / (double)numServers;
1022   }
1023 
1024   /**
1025    * This is an EXPENSIVE clone.  Cloning though is the safest thing to do.
1026    * Can't let out original since it can change and at least the load balancer
1027    * wants to iterate this exported list.  We need to synchronize on regions
1028    * since all access to this.servers is under a lock on this.regions.
1029    *
1030    * @return A clone of current assignments by table.
1031    */
1032   protected Map<TableName, Map<ServerName, List<HRegionInfo>>>
1033       getAssignmentsByTable() {
1034     Map<TableName, Map<ServerName, List<HRegionInfo>>> result =
1035       new HashMap<TableName, Map<ServerName,List<HRegionInfo>>>();
1036     synchronized (this) {
1037       if (!server.getConfiguration().getBoolean(
1038             HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE, false)) {
1039         Map<ServerName, List<HRegionInfo>> svrToRegions =
1040           new HashMap<ServerName, List<HRegionInfo>>(serverHoldings.size());
1041         for (Map.Entry<ServerName, Set<HRegionInfo>> e: serverHoldings.entrySet()) {
1042           svrToRegions.put(e.getKey(), new ArrayList<HRegionInfo>(e.getValue()));
1043         }
1044         result.put(TableName.valueOf(HConstants.ENSEMBLE_TABLE_NAME), svrToRegions);
1045       } else {
1046         for (Map.Entry<ServerName, Set<HRegionInfo>> e: serverHoldings.entrySet()) {
1047           for (HRegionInfo hri: e.getValue()) {
1048             if (hri.isMetaRegion()) continue;
1049             TableName tablename = hri.getTable();
1050             Map<ServerName, List<HRegionInfo>> svrToRegions = result.get(tablename);
1051             if (svrToRegions == null) {
1052               svrToRegions = new HashMap<ServerName, List<HRegionInfo>>(serverHoldings.size());
1053               result.put(tablename, svrToRegions);
1054             }
1055             List<HRegionInfo> regions = svrToRegions.get(e.getKey());
1056             if (regions == null) {
1057               regions = new ArrayList<HRegionInfo>();
1058               svrToRegions.put(e.getKey(), regions);
1059             }
1060             regions.add(hri);
1061           }
1062         }
1063       }
1064     }
1065 
1066     Map<ServerName, ServerLoad>
1067       onlineSvrs = serverManager.getOnlineServers();
1068     // Take care of servers w/o assignments, and remove servers in draining mode
1069     List<ServerName> drainingServers = this.serverManager.getDrainingServersList();
1070     for (Map<ServerName, List<HRegionInfo>> map: result.values()) {
1071       for (ServerName svr: onlineSvrs.keySet()) {
1072         if (!map.containsKey(svr)) {
1073           map.put(svr, new ArrayList<HRegionInfo>());
1074         }
1075       }
1076       map.keySet().removeAll(drainingServers);
1077     }
1078     return result;
1079   }
1080 
1081   protected RegionState getRegionState(final HRegionInfo hri) {
1082     return getRegionState(hri.getEncodedName());
1083   }
1084 
1085   /**
1086    * Returns a clone of region assignments per server
1087    * @return a Map of ServerName to a List of HRegionInfo's
1088    */
1089   protected synchronized Map<ServerName, List<HRegionInfo>> getRegionAssignmentsByServer() {
1090     Map<ServerName, List<HRegionInfo>> regionsByServer =
1091         new HashMap<ServerName, List<HRegionInfo>>(serverHoldings.size());
1092     for (Map.Entry<ServerName, Set<HRegionInfo>> e: serverHoldings.entrySet()) {
1093       regionsByServer.put(e.getKey(), new ArrayList<HRegionInfo>(e.getValue()));
1094     }
1095     return regionsByServer;
1096   }
1097 
1098   protected synchronized RegionState getRegionState(final String encodedName) {
1099     return regionStates.get(encodedName);
1100   }
1101 
1102   /**
1103    * Get the HRegionInfo from cache, if not there, from the hbase:meta table
1104    * @param  regionName
1105    * @return HRegionInfo for the region
1106    */
1107   @SuppressWarnings("deprecation")
1108   protected HRegionInfo getRegionInfo(final byte [] regionName) {
1109     String encodedName = HRegionInfo.encodeRegionName(regionName);
1110     RegionState regionState = getRegionState(encodedName);
1111     if (regionState != null) {
1112       return regionState.getRegion();
1113     }
1114 
1115     try {
1116       Pair<HRegionInfo, ServerName> p =
1117         MetaTableAccessor.getRegion(server.getConnection(), regionName);
1118       HRegionInfo hri = p == null ? null : p.getFirst();
1119       if (hri != null) {
1120         createRegionState(hri);
1121       }
1122       return hri;
1123     } catch (IOException e) {
1124       server.abort("Aborting because error occurred while reading "
1125         + Bytes.toStringBinary(regionName) + " from hbase:meta", e);
1126       return null;
1127     }
1128   }
1129 
1130   static boolean isOneOfStates(RegionState regionState, State... states) {
1131     State s = regionState != null ? regionState.getState() : null;
1132     for (State state: states) {
1133       if (s == state) return true;
1134     }
1135     return false;
1136   }
1137 
1138   /**
1139    * Update a region state. It will be put in transition if not already there.
1140    */
1141   private RegionState updateRegionState(final HRegionInfo hri,
1142       final State state, final ServerName serverName, long openSeqNum) {
1143     if (state == State.FAILED_CLOSE || state == State.FAILED_OPEN) {
1144       LOG.warn("Failed to open/close " + hri.getShortNameToLog()
1145         + " on " + serverName + ", set to " + state);
1146     }
1147 
1148     String encodedName = hri.getEncodedName();
1149     RegionState regionState = new RegionState(
1150       hri, state, System.currentTimeMillis(), serverName);
1151     RegionState oldState = getRegionState(encodedName);
1152     if (!regionState.equals(oldState)) {
1153       LOG.info("Transition " + oldState + " to " + regionState);
1154       // Persist region state before updating in-memory info, if needed
1155       regionStateStore.updateRegionState(openSeqNum, regionState, oldState);
1156     }
1157 
1158     synchronized (this) {
1159       regionsInTransition.put(encodedName, regionState);
1160       putRegionState(regionState);
1161 
1162       // For these states, region should be properly closed.
1163       // There should be no log splitting issue.
1164       if ((state == State.CLOSED || state == State.MERGED
1165           || state == State.SPLIT) && lastAssignments.containsKey(encodedName)) {
1166         ServerName last = lastAssignments.get(encodedName);
1167         if (last.equals(serverName)) {
1168           lastAssignments.remove(encodedName);
1169         } else {
1170           LOG.warn(encodedName + " moved to " + state + " on "
1171             + serverName + ", expected " + last);
1172         }
1173       }
1174 
1175       // Once a region is opened, record its last assignment right away.
1176       if (serverName != null && state == State.OPEN) {
1177         ServerName last = lastAssignments.get(encodedName);
1178         if (!serverName.equals(last)) {
1179           lastAssignments.put(encodedName, serverName);
1180           if (last != null && isServerDeadAndNotProcessed(last)) {
1181             LOG.warn(encodedName + " moved to " + serverName
1182               + ", while it's previous host " + last
1183               + " is dead but not processed yet");
1184           }
1185         }
1186       }
1187 
1188       // notify the change
1189       this.notifyAll();
1190     }
1191     return regionState;
1192   }
1193 }