001/*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.master;
020
021import static org.apache.hadoop.hbase.util.CollectionUtils.computeIfAbsent;
022
023import java.io.IOException;
024import java.net.InetAddress;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032import java.util.concurrent.ConcurrentNavigableMap;
033import java.util.concurrent.ConcurrentSkipListMap;
034import java.util.concurrent.CopyOnWriteArrayList;
035import java.util.concurrent.atomic.AtomicBoolean;
036import java.util.function.Predicate;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.hbase.ClockOutOfSyncException;
039import org.apache.hadoop.hbase.HConstants;
040import org.apache.hadoop.hbase.NotServingRegionException;
041import org.apache.hadoop.hbase.RegionMetrics;
042import org.apache.hadoop.hbase.ServerMetrics;
043import org.apache.hadoop.hbase.ServerMetricsBuilder;
044import org.apache.hadoop.hbase.ServerName;
045import org.apache.hadoop.hbase.YouAreDeadException;
046import org.apache.hadoop.hbase.client.ClusterConnection;
047import org.apache.hadoop.hbase.client.RegionInfo;
048import org.apache.hadoop.hbase.client.RetriesExhaustedException;
049import org.apache.hadoop.hbase.ipc.HBaseRpcController;
050import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
051import org.apache.hadoop.hbase.monitoring.MonitoredTask;
052import org.apache.hadoop.hbase.regionserver.HRegionServer;
053import org.apache.hadoop.hbase.util.Bytes;
054import org.apache.hadoop.hbase.zookeeper.ZKUtil;
055import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
056import org.apache.yetus.audience.InterfaceAudience;
057import org.apache.zookeeper.KeeperException;
058import org.slf4j.Logger;
059import org.slf4j.LoggerFactory;
060
061import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
062import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
063
064import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
065import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.AdminService;
066import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.RegionStoreSequenceIds;
067import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.StoreSequenceId;
068import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionServerStartupRequest;
069
070/**
071 * The ServerManager class manages info about region servers.
072 * <p>
073 * Maintains lists of online and dead servers.  Processes the startups,
074 * shutdowns, and deaths of region servers.
075 * <p>
076 * Servers are distinguished in two different ways.  A given server has a
077 * location, specified by hostname and port, and of which there can only be one
078 * online at any given time.  A server instance is specified by the location
079 * (hostname and port) as well as the startcode (timestamp from when the server
080 * was started).  This is used to differentiate a restarted instance of a given
081 * server from the original instance.
082 * <p>
083 * If a sever is known not to be running any more, it is called dead. The dead
084 * server needs to be handled by a ServerShutdownHandler.  If the handler is not
085 * enabled yet, the server can't be handled right away so it is queued up.
086 * After the handler is enabled, the server will be submitted to a handler to handle.
087 * However, the handler may be just partially enabled.  If so,
088 * the server cannot be fully processed, and be queued up for further processing.
089 * A server is fully processed only after the handler is fully enabled
090 * and has completed the handling.
091 */
092@InterfaceAudience.Private
093public class ServerManager {
094  public static final String WAIT_ON_REGIONSERVERS_MAXTOSTART =
095      "hbase.master.wait.on.regionservers.maxtostart";
096
097  public static final String WAIT_ON_REGIONSERVERS_MINTOSTART =
098      "hbase.master.wait.on.regionservers.mintostart";
099
100  public static final String WAIT_ON_REGIONSERVERS_TIMEOUT =
101      "hbase.master.wait.on.regionservers.timeout";
102
103  public static final String WAIT_ON_REGIONSERVERS_INTERVAL =
104      "hbase.master.wait.on.regionservers.interval";
105
106  private static final Logger LOG = LoggerFactory.getLogger(ServerManager.class);
107
108  // Set if we are to shutdown the cluster.
109  private AtomicBoolean clusterShutdown = new AtomicBoolean(false);
110
111  /**
112   * The last flushed sequence id for a region.
113   */
114  private final ConcurrentNavigableMap<byte[], Long> flushedSequenceIdByRegion =
115    new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR);
116
117  /**
118   * The last flushed sequence id for a store in a region.
119   */
120  private final ConcurrentNavigableMap<byte[], ConcurrentNavigableMap<byte[], Long>>
121    storeFlushedSequenceIdsByRegion = new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR);
122
123  /** Map of registered servers to their current load */
124  private final ConcurrentNavigableMap<ServerName, ServerMetrics> onlineServers =
125    new ConcurrentSkipListMap<>();
126
127  /**
128   * Map of admin interfaces per registered regionserver; these interfaces we use to control
129   * regionservers out on the cluster
130   */
131  private final Map<ServerName, AdminService.BlockingInterface> rsAdmins = new HashMap<>();
132
133  /** List of region servers that should not get any more new regions. */
134  private final ArrayList<ServerName> drainingServers = new ArrayList<>();
135
136  private final MasterServices master;
137  private final ClusterConnection connection;
138
139  private final DeadServer deadservers = new DeadServer();
140
141  private final long maxSkew;
142  private final long warningSkew;
143
144  private final RpcControllerFactory rpcControllerFactory;
145
146  /** Listeners that are called on server events. */
147  private List<ServerListener> listeners = new CopyOnWriteArrayList<>();
148
149  /**
150   * Constructor.
151   */
152  public ServerManager(final MasterServices master) {
153    this.master = master;
154    Configuration c = master.getConfiguration();
155    maxSkew = c.getLong("hbase.master.maxclockskew", 30000);
156    warningSkew = c.getLong("hbase.master.warningclockskew", 10000);
157    this.connection = master.getClusterConnection();
158    this.rpcControllerFactory = this.connection == null? null: connection.getRpcControllerFactory();
159  }
160
161  /**
162   * Add the listener to the notification list.
163   * @param listener The ServerListener to register
164   */
165  public void registerListener(final ServerListener listener) {
166    this.listeners.add(listener);
167  }
168
169  /**
170   * Remove the listener from the notification list.
171   * @param listener The ServerListener to unregister
172   */
173  public boolean unregisterListener(final ServerListener listener) {
174    return this.listeners.remove(listener);
175  }
176
177  /**
178   * Let the server manager know a new regionserver has come online
179   * @param request the startup request
180   * @param versionNumber the version of the new regionserver
181   * @param ia the InetAddress from which request is received
182   * @return The ServerName we know this server as.
183   * @throws IOException
184   */
185  ServerName regionServerStartup(RegionServerStartupRequest request, int versionNumber,
186      InetAddress ia) throws IOException {
187    // Test for case where we get a region startup message from a regionserver
188    // that has been quickly restarted but whose znode expiration handler has
189    // not yet run, or from a server whose fail we are currently processing.
190    // Test its host+port combo is present in serverAddressToServerInfo.  If it
191    // is, reject the server and trigger its expiration. The next time it comes
192    // in, it should have been removed from serverAddressToServerInfo and queued
193    // for processing by ProcessServerShutdown.
194
195    final String hostname = request.hasUseThisHostnameInstead() ?
196        request.getUseThisHostnameInstead() :ia.getHostName();
197    ServerName sn = ServerName.valueOf(hostname, request.getPort(),
198      request.getServerStartCode());
199    checkClockSkew(sn, request.getServerCurrentTime());
200    checkIsDead(sn, "STARTUP");
201    if (!checkAndRecordNewServer(sn, ServerMetricsBuilder.of(sn, versionNumber))) {
202      LOG.warn("THIS SHOULD NOT HAPPEN, RegionServerStartup"
203        + " could not record the server: " + sn);
204    }
205    return sn;
206  }
207
208  /**
209   * Updates last flushed sequence Ids for the regions on server sn
210   * @param sn
211   * @param hsl
212   */
213  private void updateLastFlushedSequenceIds(ServerName sn, ServerMetrics hsl) {
214    for (Entry<byte[], RegionMetrics> entry : hsl.getRegionMetrics().entrySet()) {
215      byte[] encodedRegionName = Bytes.toBytes(RegionInfo.encodeRegionName(entry.getKey()));
216      Long existingValue = flushedSequenceIdByRegion.get(encodedRegionName);
217      long l = entry.getValue().getCompletedSequenceId();
218      // Don't let smaller sequence ids override greater sequence ids.
219      if (LOG.isTraceEnabled()) {
220        LOG.trace(Bytes.toString(encodedRegionName) + ", existingValue=" + existingValue +
221          ", completeSequenceId=" + l);
222      }
223      if (existingValue == null || (l != HConstants.NO_SEQNUM && l > existingValue)) {
224        flushedSequenceIdByRegion.put(encodedRegionName, l);
225      } else if (l != HConstants.NO_SEQNUM && l < existingValue) {
226        LOG.warn("RegionServer " + sn + " indicates a last flushed sequence id ("
227            + l + ") that is less than the previous last flushed sequence id ("
228            + existingValue + ") for region " + Bytes.toString(entry.getKey()) + " Ignoring.");
229      }
230      ConcurrentNavigableMap<byte[], Long> storeFlushedSequenceId =
231          computeIfAbsent(storeFlushedSequenceIdsByRegion, encodedRegionName,
232            () -> new ConcurrentSkipListMap<>(Bytes.BYTES_COMPARATOR));
233      for (Entry<byte[], Long> storeSeqId : entry.getValue().getStoreSequenceId().entrySet()) {
234        byte[] family = storeSeqId.getKey();
235        existingValue = storeFlushedSequenceId.get(family);
236        l = storeSeqId.getValue();
237        if (LOG.isTraceEnabled()) {
238          LOG.trace(Bytes.toString(encodedRegionName) + ", family=" + Bytes.toString(family) +
239            ", existingValue=" + existingValue + ", completeSequenceId=" + l);
240        }
241        // Don't let smaller sequence ids override greater sequence ids.
242        if (existingValue == null || (l != HConstants.NO_SEQNUM && l > existingValue.longValue())) {
243          storeFlushedSequenceId.put(family, l);
244        }
245      }
246    }
247  }
248
249  @VisibleForTesting
250  public void regionServerReport(ServerName sn,
251    ServerMetrics sl) throws YouAreDeadException {
252    checkIsDead(sn, "REPORT");
253    if (null == this.onlineServers.replace(sn, sl)) {
254      // Already have this host+port combo and its just different start code?
255      // Just let the server in. Presume master joining a running cluster.
256      // recordNewServer is what happens at the end of reportServerStartup.
257      // The only thing we are skipping is passing back to the regionserver
258      // the ServerName to use. Here we presume a master has already done
259      // that so we'll press on with whatever it gave us for ServerName.
260      if (!checkAndRecordNewServer(sn, sl)) {
261        LOG.info("RegionServerReport ignored, could not record the server: " + sn);
262        return; // Not recorded, so no need to move on
263      }
264    }
265    updateLastFlushedSequenceIds(sn, sl);
266  }
267
268  /**
269   * Check is a server of same host and port already exists,
270   * if not, or the existed one got a smaller start code, record it.
271   *
272   * @param serverName the server to check and record
273   * @param sl the server load on the server
274   * @return true if the server is recorded, otherwise, false
275   */
276  boolean checkAndRecordNewServer(final ServerName serverName, final ServerMetrics sl) {
277    ServerName existingServer = null;
278    synchronized (this.onlineServers) {
279      existingServer = findServerWithSameHostnamePortWithLock(serverName);
280      if (existingServer != null && (existingServer.getStartcode() > serverName.getStartcode())) {
281        LOG.info("Server serverName=" + serverName + " rejected; we already have "
282            + existingServer.toString() + " registered with same hostname and port");
283        return false;
284      }
285      recordNewServerWithLock(serverName, sl);
286    }
287
288    // Tell our listeners that a server was added
289    if (!this.listeners.isEmpty()) {
290      for (ServerListener listener : this.listeners) {
291        listener.serverAdded(serverName);
292      }
293    }
294
295    // Note that we assume that same ts means same server, and don't expire in that case.
296    //  TODO: ts can theoretically collide due to clock shifts, so this is a bit hacky.
297    if (existingServer != null &&
298        (existingServer.getStartcode() < serverName.getStartcode())) {
299      LOG.info("Triggering server recovery; existingServer " +
300          existingServer + " looks stale, new server:" + serverName);
301      expireServer(existingServer);
302    }
303    return true;
304  }
305
306  /**
307   * Find out the region servers crashed between the crash of the previous master instance and the
308   * current master instance and schedule SCP for them.
309   * <p/>
310   * Since the {@code RegionServerTracker} has already helped us to construct the online servers set
311   * by scanning zookeeper, now we can compare the online servers with {@code liveServersFromWALDir}
312   * to find out whether there are servers which are already dead.
313   * <p/>
314   * Must be called inside the initialization method of {@code RegionServerTracker} to avoid
315   * concurrency issue.
316   * @param deadServersFromPE the region servers which already have SCP associated.
317   * @param liveServersFromWALDir the live region servers from wal directory.
318   */
319  void findOutDeadServersAndProcess(Set<ServerName> deadServersFromPE,
320      Set<ServerName> liveServersFromWALDir) {
321    deadServersFromPE.forEach(deadservers::add);
322    liveServersFromWALDir.stream().filter(sn -> !onlineServers.containsKey(sn))
323      .forEach(this::expireServer);
324  }
325
326  /**
327   * Checks if the clock skew between the server and the master. If the clock skew exceeds the
328   * configured max, it will throw an exception; if it exceeds the configured warning threshold,
329   * it will log a warning but start normally.
330   * @param serverName Incoming servers's name
331   * @param serverCurrentTime
332   * @throws ClockOutOfSyncException if the skew exceeds the configured max value
333   */
334  private void checkClockSkew(final ServerName serverName, final long serverCurrentTime)
335      throws ClockOutOfSyncException {
336    long skew = Math.abs(System.currentTimeMillis() - serverCurrentTime);
337    if (skew > maxSkew) {
338      String message = "Server " + serverName + " has been " +
339        "rejected; Reported time is too far out of sync with master.  " +
340        "Time difference of " + skew + "ms > max allowed of " + maxSkew + "ms";
341      LOG.warn(message);
342      throw new ClockOutOfSyncException(message);
343    } else if (skew > warningSkew){
344      String message = "Reported time for server " + serverName + " is out of sync with master " +
345        "by " + skew + "ms. (Warning threshold is " + warningSkew + "ms; " +
346        "error threshold is " + maxSkew + "ms)";
347      LOG.warn(message);
348    }
349  }
350
351  /**
352   * If this server is on the dead list, reject it with a YouAreDeadException.
353   * If it was dead but came back with a new start code, remove the old entry
354   * from the dead list.
355   * @param what START or REPORT
356   */
357  private void checkIsDead(final ServerName serverName, final String what)
358      throws YouAreDeadException {
359    if (this.deadservers.isDeadServer(serverName)) {
360      // host name, port and start code all match with existing one of the
361      // dead servers. So, this server must be dead.
362      String message = "Server " + what + " rejected; currently processing " +
363          serverName + " as dead server";
364      LOG.debug(message);
365      throw new YouAreDeadException(message);
366    }
367    // remove dead server with same hostname and port of newly checking in rs after master
368    // initialization.See HBASE-5916 for more information.
369    if ((this.master == null || this.master.isInitialized())
370        && this.deadservers.cleanPreviousInstance(serverName)) {
371      // This server has now become alive after we marked it as dead.
372      // We removed it's previous entry from the dead list to reflect it.
373      LOG.debug(what + ":" + " Server " + serverName + " came back up," +
374          " removed it from the dead servers list");
375    }
376  }
377
378  /**
379   * Assumes onlineServers is locked.
380   * @return ServerName with matching hostname and port.
381   */
382  private ServerName findServerWithSameHostnamePortWithLock(
383      final ServerName serverName) {
384    ServerName end = ServerName.valueOf(serverName.getHostname(), serverName.getPort(),
385        Long.MAX_VALUE);
386
387    ServerName r = onlineServers.lowerKey(end);
388    if (r != null) {
389      if (ServerName.isSameAddress(r, serverName)) {
390        return r;
391      }
392    }
393    return null;
394  }
395
396  /**
397   * Adds the onlineServers list. onlineServers should be locked.
398   * @param serverName The remote servers name.
399   */
400  @VisibleForTesting
401  void recordNewServerWithLock(final ServerName serverName, final ServerMetrics sl) {
402    LOG.info("Registering regionserver=" + serverName);
403    this.onlineServers.put(serverName, sl);
404    this.rsAdmins.remove(serverName);
405  }
406
407  public RegionStoreSequenceIds getLastFlushedSequenceId(byte[] encodedRegionName) {
408    RegionStoreSequenceIds.Builder builder = RegionStoreSequenceIds.newBuilder();
409    Long seqId = flushedSequenceIdByRegion.get(encodedRegionName);
410    builder.setLastFlushedSequenceId(seqId != null ? seqId.longValue() : HConstants.NO_SEQNUM);
411    Map<byte[], Long> storeFlushedSequenceId =
412        storeFlushedSequenceIdsByRegion.get(encodedRegionName);
413    if (storeFlushedSequenceId != null) {
414      for (Map.Entry<byte[], Long> entry : storeFlushedSequenceId.entrySet()) {
415        builder.addStoreSequenceId(StoreSequenceId.newBuilder()
416            .setFamilyName(UnsafeByteOperations.unsafeWrap(entry.getKey()))
417            .setSequenceId(entry.getValue().longValue()).build());
418      }
419    }
420    return builder.build();
421  }
422
423  /**
424   * @param serverName
425   * @return ServerMetrics if serverName is known else null
426   */
427  public ServerMetrics getLoad(final ServerName serverName) {
428    return this.onlineServers.get(serverName);
429  }
430
431  /**
432   * Compute the average load across all region servers.
433   * Currently, this uses a very naive computation - just uses the number of
434   * regions being served, ignoring stats about number of requests.
435   * @return the average load
436   */
437  public double getAverageLoad() {
438    int totalLoad = 0;
439    int numServers = 0;
440    for (ServerMetrics sl : this.onlineServers.values()) {
441      numServers++;
442      totalLoad += sl.getRegionMetrics().size();
443    }
444    return numServers == 0 ? 0 :
445      (double)totalLoad / (double)numServers;
446  }
447
448  /** @return the count of active regionservers */
449  public int countOfRegionServers() {
450    // Presumes onlineServers is a concurrent map
451    return this.onlineServers.size();
452  }
453
454  /**
455   * @return Read-only map of servers to serverinfo
456   */
457  public Map<ServerName, ServerMetrics> getOnlineServers() {
458    // Presumption is that iterating the returned Map is OK.
459    synchronized (this.onlineServers) {
460      return Collections.unmodifiableMap(this.onlineServers);
461    }
462  }
463
464  public DeadServer getDeadServers() {
465    return this.deadservers;
466  }
467
468  /**
469   * Checks if any dead servers are currently in progress.
470   * @return true if any RS are being processed as dead, false if not
471   */
472  public boolean areDeadServersInProgress() {
473    return this.deadservers.areDeadServersInProgress();
474  }
475
476  void letRegionServersShutdown() {
477    long previousLogTime = 0;
478    ServerName sn = master.getServerName();
479    ZKWatcher zkw = master.getZooKeeper();
480    int onlineServersCt;
481    while ((onlineServersCt = onlineServers.size()) > 0){
482
483      if (System.currentTimeMillis() > (previousLogTime + 1000)) {
484        Set<ServerName> remainingServers = onlineServers.keySet();
485        synchronized (onlineServers) {
486          if (remainingServers.size() == 1 && remainingServers.contains(sn)) {
487            // Master will delete itself later.
488            return;
489          }
490        }
491        StringBuilder sb = new StringBuilder();
492        // It's ok here to not sync on onlineServers - merely logging
493        for (ServerName key : remainingServers) {
494          if (sb.length() > 0) {
495            sb.append(", ");
496          }
497          sb.append(key);
498        }
499        LOG.info("Waiting on regionserver(s) " + sb.toString());
500        previousLogTime = System.currentTimeMillis();
501      }
502
503      try {
504        List<String> servers = getRegionServersInZK(zkw);
505        if (servers == null || servers.isEmpty() || (servers.size() == 1
506            && servers.contains(sn.toString()))) {
507          LOG.info("ZK shows there is only the master self online, exiting now");
508          // Master could have lost some ZK events, no need to wait more.
509          break;
510        }
511      } catch (KeeperException ke) {
512        LOG.warn("Failed to list regionservers", ke);
513        // ZK is malfunctioning, don't hang here
514        break;
515      }
516      synchronized (onlineServers) {
517        try {
518          if (onlineServersCt == onlineServers.size()) onlineServers.wait(100);
519        } catch (InterruptedException ignored) {
520          // continue
521        }
522      }
523    }
524  }
525
526  private List<String> getRegionServersInZK(final ZKWatcher zkw)
527  throws KeeperException {
528    return ZKUtil.listChildrenNoWatch(zkw, zkw.znodePaths.rsZNode);
529  }
530
531  /**
532   * Expire the passed server. Add it to list of dead servers and queue a shutdown processing.
533   * @return True if we queued a ServerCrashProcedure else false if we did not (could happen for
534   *         many reasons including the fact that its this server that is going down or we already
535   *         have queued an SCP for this server or SCP processing is currently disabled because we
536   *         are in startup phase).
537   */
538  public synchronized boolean expireServer(final ServerName serverName) {
539    // THIS server is going down... can't handle our own expiration.
540    if (serverName.equals(master.getServerName())) {
541      if (!(master.isAborted() || master.isStopped())) {
542        master.stop("We lost our znode?");
543      }
544      return false;
545    }
546    if (this.deadservers.isDeadServer(serverName)) {
547      LOG.warn("Expiration called on {} but crash processing already in progress", serverName);
548      return false;
549    }
550    moveFromOnlineToDeadServers(serverName);
551
552    // If cluster is going down, yes, servers are going to be expiring; don't
553    // process as a dead server
554    if (isClusterShutdown()) {
555      LOG.info("Cluster shutdown set; " + serverName +
556        " expired; onlineServers=" + this.onlineServers.size());
557      if (this.onlineServers.isEmpty()) {
558        master.stop("Cluster shutdown set; onlineServer=0");
559      }
560      return false;
561    }
562    LOG.info("Processing expiration of " + serverName + " on " + this.master.getServerName());
563    master.getAssignmentManager().submitServerCrash(serverName, true);
564
565    // Tell our listeners that a server was removed
566    if (!this.listeners.isEmpty()) {
567      for (ServerListener listener : this.listeners) {
568        listener.serverRemoved(serverName);
569      }
570    }
571    return true;
572  }
573
574  @VisibleForTesting
575  public void moveFromOnlineToDeadServers(final ServerName sn) {
576    synchronized (onlineServers) {
577      if (!this.onlineServers.containsKey(sn)) {
578        LOG.warn("Expiration of " + sn + " but server not online");
579      }
580      // Remove the server from the known servers lists and update load info BUT
581      // add to deadservers first; do this so it'll show in dead servers list if
582      // not in online servers list.
583      this.deadservers.add(sn);
584      this.onlineServers.remove(sn);
585      onlineServers.notifyAll();
586    }
587    this.rsAdmins.remove(sn);
588  }
589
590  /*
591   * Remove the server from the drain list.
592   */
593  public synchronized boolean removeServerFromDrainList(final ServerName sn) {
594    // Warn if the server (sn) is not online.  ServerName is of the form:
595    // <hostname> , <port> , <startcode>
596
597    if (!this.isServerOnline(sn)) {
598      LOG.warn("Server " + sn + " is not currently online. " +
599               "Removing from draining list anyway, as requested.");
600    }
601    // Remove the server from the draining servers lists.
602    return this.drainingServers.remove(sn);
603  }
604
605  /**
606   * Add the server to the drain list.
607   * @param sn
608   * @return True if the server is added or the server is already on the drain list.
609   */
610  public synchronized boolean addServerToDrainList(final ServerName sn) {
611    // Warn if the server (sn) is not online.  ServerName is of the form:
612    // <hostname> , <port> , <startcode>
613
614    if (!this.isServerOnline(sn)) {
615      LOG.warn("Server " + sn + " is not currently online. " +
616               "Ignoring request to add it to draining list.");
617      return false;
618    }
619    // Add the server to the draining servers lists, if it's not already in
620    // it.
621    if (this.drainingServers.contains(sn)) {
622      LOG.warn("Server " + sn + " is already in the draining server list." +
623               "Ignoring request to add it again.");
624      return true;
625    }
626    LOG.info("Server " + sn + " added to draining server list.");
627    return this.drainingServers.add(sn);
628  }
629
630  // RPC methods to region servers
631
632  private HBaseRpcController newRpcController() {
633    return rpcControllerFactory == null ? null : rpcControllerFactory.newController();
634  }
635
636  /**
637   * Sends a WARMUP RPC to the specified server to warmup the specified region.
638   * <p>
639   * A region server could reject the close request because it either does not
640   * have the specified region or the region is being split.
641   * @param server server to warmup a region
642   * @param region region to  warmup
643   */
644  public void sendRegionWarmup(ServerName server,
645      RegionInfo region) {
646    if (server == null) return;
647    try {
648      AdminService.BlockingInterface admin = getRsAdmin(server);
649      HBaseRpcController controller = newRpcController();
650      ProtobufUtil.warmupRegion(controller, admin, region);
651    } catch (IOException e) {
652      LOG.error("Received exception in RPC for warmup server:" +
653        server + "region: " + region +
654        "exception: " + e);
655    }
656  }
657
658  /**
659   * Contacts a region server and waits up to timeout ms
660   * to close the region.  This bypasses the active hmaster.
661   */
662  public static void closeRegionSilentlyAndWait(ClusterConnection connection,
663    ServerName server, RegionInfo region, long timeout) throws IOException, InterruptedException {
664    AdminService.BlockingInterface rs = connection.getAdmin(server);
665    HBaseRpcController controller = connection.getRpcControllerFactory().newController();
666    try {
667      ProtobufUtil.closeRegion(controller, rs, server, region.getRegionName());
668    } catch (IOException e) {
669      LOG.warn("Exception when closing region: " + region.getRegionNameAsString(), e);
670    }
671    long expiration = timeout + System.currentTimeMillis();
672    while (System.currentTimeMillis() < expiration) {
673      controller.reset();
674      try {
675        RegionInfo rsRegion =
676          ProtobufUtil.getRegionInfo(controller, rs, region.getRegionName());
677        if (rsRegion == null) return;
678      } catch (IOException ioe) {
679        if (ioe instanceof NotServingRegionException) // no need to retry again
680          return;
681        LOG.warn("Exception when retrieving regioninfo from: "
682          + region.getRegionNameAsString(), ioe);
683      }
684      Thread.sleep(1000);
685    }
686    throw new IOException("Region " + region + " failed to close within"
687        + " timeout " + timeout);
688  }
689
690  /**
691   * @param sn
692   * @return Admin interface for the remote regionserver named <code>sn</code>
693   * @throws IOException
694   * @throws RetriesExhaustedException wrapping a ConnectException if failed
695   */
696  public AdminService.BlockingInterface getRsAdmin(final ServerName sn)
697  throws IOException {
698    AdminService.BlockingInterface admin = this.rsAdmins.get(sn);
699    if (admin == null) {
700      LOG.debug("New admin connection to " + sn.toString());
701      if (sn.equals(master.getServerName()) && master instanceof HRegionServer) {
702        // A master is also a region server now, see HBASE-10569 for details
703        admin = ((HRegionServer)master).getRSRpcServices();
704      } else {
705        admin = this.connection.getAdmin(sn);
706      }
707      this.rsAdmins.put(sn, admin);
708    }
709    return admin;
710  }
711
712  /**
713   * Calculate min necessary to start. This is not an absolute. It is just
714   * a friction that will cause us hang around a bit longer waiting on
715   * RegionServers to check-in.
716   */
717  private int getMinToStart() {
718    // One server should be enough to get us off the ground.
719    int requiredMinToStart = 1;
720    if (LoadBalancer.isTablesOnMaster(master.getConfiguration())) {
721      if (LoadBalancer.isSystemTablesOnlyOnMaster(master.getConfiguration())) {
722        // If Master is carrying regions but NOT user-space regions, it
723        // still shows as a 'server'. We need at least one more server to check
724        // in before we can start up so set defaultMinToStart to 2.
725        requiredMinToStart = requiredMinToStart + 1;
726      }
727    }
728    int minToStart = this.master.getConfiguration().getInt(WAIT_ON_REGIONSERVERS_MINTOSTART, -1);
729    // Ensure we are never less than requiredMinToStart else stuff won't work.
730    return minToStart == -1 || minToStart < requiredMinToStart? requiredMinToStart: minToStart;
731  }
732
733  /**
734   * Wait for the region servers to report in.
735   * We will wait until one of this condition is met:
736   *  - the master is stopped
737   *  - the 'hbase.master.wait.on.regionservers.maxtostart' number of
738   *    region servers is reached
739   *  - the 'hbase.master.wait.on.regionservers.mintostart' is reached AND
740   *   there have been no new region server in for
741   *      'hbase.master.wait.on.regionservers.interval' time AND
742   *   the 'hbase.master.wait.on.regionservers.timeout' is reached
743   *
744   * @throws InterruptedException
745   */
746  public void waitForRegionServers(MonitoredTask status) throws InterruptedException {
747    final long interval = this.master.getConfiguration().
748        getLong(WAIT_ON_REGIONSERVERS_INTERVAL, 1500);
749    final long timeout = this.master.getConfiguration().
750        getLong(WAIT_ON_REGIONSERVERS_TIMEOUT, 4500);
751    // Min is not an absolute; just a friction making us wait longer on server checkin.
752    int minToStart = getMinToStart();
753    int maxToStart = this.master.getConfiguration().
754        getInt(WAIT_ON_REGIONSERVERS_MAXTOSTART, Integer.MAX_VALUE);
755    if (maxToStart < minToStart) {
756      LOG.warn(String.format("The value of '%s' (%d) is set less than '%s' (%d), ignoring.",
757          WAIT_ON_REGIONSERVERS_MAXTOSTART, maxToStart,
758          WAIT_ON_REGIONSERVERS_MINTOSTART, minToStart));
759      maxToStart = Integer.MAX_VALUE;
760    }
761
762    long now =  System.currentTimeMillis();
763    final long startTime = now;
764    long slept = 0;
765    long lastLogTime = 0;
766    long lastCountChange = startTime;
767    int count = countOfRegionServers();
768    int oldCount = 0;
769    // This while test is a little hard to read. We try to comment it in below but in essence:
770    // Wait if Master is not stopped and the number of regionservers that have checked-in is
771    // less than the maxToStart. Both of these conditions will be true near universally.
772    // Next, we will keep cycling if ANY of the following three conditions are true:
773    // 1. The time since a regionserver registered is < interval (means servers are actively checking in).
774    // 2. We are under the total timeout.
775    // 3. The count of servers is < minimum.
776    for (ServerListener listener: this.listeners) {
777      listener.waiting();
778    }
779    while (!this.master.isStopped() && !isClusterShutdown() && count < maxToStart &&
780        ((lastCountChange + interval) > now || timeout > slept || count < minToStart)) {
781      // Log some info at every interval time or if there is a change
782      if (oldCount != count || lastLogTime + interval < now) {
783        lastLogTime = now;
784        String msg =
785            "Waiting on regionserver count=" + count + "; waited="+
786                slept + "ms, expecting min=" + minToStart + " server(s), max="+ getStrForMax(maxToStart) +
787                " server(s), " + "timeout=" + timeout + "ms, lastChange=" + (lastCountChange - now) + "ms";
788        LOG.info(msg);
789        status.setStatus(msg);
790      }
791
792      // We sleep for some time
793      final long sleepTime = 50;
794      Thread.sleep(sleepTime);
795      now =  System.currentTimeMillis();
796      slept = now - startTime;
797
798      oldCount = count;
799      count = countOfRegionServers();
800      if (count != oldCount) {
801        lastCountChange = now;
802      }
803    }
804    // Did we exit the loop because cluster is going down?
805    if (isClusterShutdown()) {
806      this.master.stop("Cluster shutdown");
807    }
808    LOG.info("Finished waiting on RegionServer count=" + count + "; waited=" + slept + "ms," +
809        " expected min=" + minToStart + " server(s), max=" +  getStrForMax(maxToStart) + " server(s),"+
810        " master is "+ (this.master.isStopped() ? "stopped.": "running"));
811  }
812
813  private String getStrForMax(final int max) {
814    return max == Integer.MAX_VALUE? "NO_LIMIT": Integer.toString(max);
815  }
816
817  /**
818   * @return A copy of the internal list of online servers.
819   */
820  public List<ServerName> getOnlineServersList() {
821    // TODO: optimize the load balancer call so we don't need to make a new list
822    // TODO: FIX. THIS IS POPULAR CALL.
823    return new ArrayList<>(this.onlineServers.keySet());
824  }
825
826  /**
827   * @param keys The target server name
828   * @param idleServerPredicator Evaluates the server on the given load
829   * @return A copy of the internal list of online servers matched by the predicator
830   */
831  public List<ServerName> getOnlineServersListWithPredicator(List<ServerName> keys,
832    Predicate<ServerMetrics> idleServerPredicator) {
833    List<ServerName> names = new ArrayList<>();
834    if (keys != null && idleServerPredicator != null) {
835      keys.forEach(name -> {
836        ServerMetrics load = onlineServers.get(name);
837        if (load != null) {
838          if (idleServerPredicator.test(load)) {
839            names.add(name);
840          }
841        }
842      });
843    }
844    return names;
845  }
846
847  /**
848   * @return A copy of the internal list of draining servers.
849   */
850  public List<ServerName> getDrainingServersList() {
851    return new ArrayList<>(this.drainingServers);
852  }
853
854  public boolean isServerOnline(ServerName serverName) {
855    return serverName != null && onlineServers.containsKey(serverName);
856  }
857
858  /**
859   * Check if a server is known to be dead.  A server can be online,
860   * or known to be dead, or unknown to this manager (i.e, not online,
861   * not known to be dead either. it is simply not tracked by the
862   * master any more, for example, a very old previous instance).
863   */
864  public synchronized boolean isServerDead(ServerName serverName) {
865    return serverName == null || deadservers.isDeadServer(serverName);
866  }
867
868  public void shutdownCluster() {
869    String statusStr = "Cluster shutdown requested of master=" + this.master.getServerName();
870    LOG.info(statusStr);
871    this.clusterShutdown.set(true);
872    if (onlineServers.isEmpty()) {
873      // we do not synchronize here so this may cause a double stop, but not a big deal
874      master.stop("OnlineServer=0 right after cluster shutdown set");
875    }
876  }
877
878  public boolean isClusterShutdown() {
879    return this.clusterShutdown.get();
880  }
881
882  /**
883   * Stop the ServerManager.
884   */
885  public void stop() {
886    // Nothing to do.
887  }
888
889  /**
890   * Creates a list of possible destinations for a region. It contains the online servers, but not
891   *  the draining or dying servers.
892   *  @param serversToExclude can be null if there is no server to exclude
893   */
894  public List<ServerName> createDestinationServersList(final List<ServerName> serversToExclude){
895    final List<ServerName> destServers = getOnlineServersList();
896
897    if (serversToExclude != null) {
898      destServers.removeAll(serversToExclude);
899    }
900
901    // Loop through the draining server list and remove them from the server list
902    final List<ServerName> drainingServersCopy = getDrainingServersList();
903    destServers.removeAll(drainingServersCopy);
904
905    return destServers;
906  }
907
908  /**
909   * Calls {@link #createDestinationServersList} without server to exclude.
910   */
911  public List<ServerName> createDestinationServersList(){
912    return createDestinationServersList(null);
913  }
914
915  /**
916   * To clear any dead server with same host name and port of any online server
917   */
918  void clearDeadServersWithSameHostNameAndPortOfOnlineServer() {
919    for (ServerName serverName : getOnlineServersList()) {
920      deadservers.cleanAllPreviousInstances(serverName);
921    }
922  }
923
924  /**
925   * Called by delete table and similar to notify the ServerManager that a region was removed.
926   */
927  public void removeRegion(final RegionInfo regionInfo) {
928    final byte[] encodedName = regionInfo.getEncodedNameAsBytes();
929    storeFlushedSequenceIdsByRegion.remove(encodedName);
930    flushedSequenceIdByRegion.remove(encodedName);
931  }
932
933  @VisibleForTesting
934  public boolean isRegionInServerManagerStates(final RegionInfo hri) {
935    final byte[] encodedName = hri.getEncodedNameAsBytes();
936    return (storeFlushedSequenceIdsByRegion.containsKey(encodedName)
937        || flushedSequenceIdByRegion.containsKey(encodedName));
938  }
939
940  /**
941   * Called by delete table and similar to notify the ServerManager that a region was removed.
942   */
943  public void removeRegions(final List<RegionInfo> regions) {
944    for (RegionInfo hri: regions) {
945      removeRegion(hri);
946    }
947  }
948
949  /**
950   * May return 0 when server is not online.
951   */
952  public int getServerVersion(final ServerName serverName) {
953    ServerMetrics serverMetrics = onlineServers.get(serverName);
954    return serverMetrics != null ? serverMetrics.getVersionNumber() : 0;
955  }
956
957  public int getInfoPort(ServerName serverName) {
958    ServerMetrics serverMetrics = onlineServers.get(serverName);
959    return serverMetrics != null ? serverMetrics.getInfoServerPort() : 0;
960  }
961}