001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Comparator;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Objects;
026import java.util.Set;
027import java.util.TreeSet;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.ClusterManager.ServiceType;
030import org.apache.hadoop.hbase.client.Admin;
031import org.apache.hadoop.hbase.client.ClusterConnection;
032import org.apache.hadoop.hbase.client.Connection;
033import org.apache.hadoop.hbase.client.ConnectionFactory;
034import org.apache.hadoop.hbase.client.RegionLocator;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.hadoop.hbase.util.Threads;
037import org.apache.yetus.audience.InterfaceAudience;
038
039import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
040import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos;
041import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ServerInfo;
042import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
043import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MasterService;
044
045/**
046 * Manages the interactions with an already deployed distributed cluster (as opposed to
047 * a pseudo-distributed, or mini/local cluster). This is used by integration and system tests.
048 */
049@InterfaceAudience.Private
050public class DistributedHBaseCluster extends HBaseCluster {
051  private Admin admin;
052  private final Connection connection;
053
054  private ClusterManager clusterManager;
055  /**
056   * List of RegionServers killed so far. ServerName also comprises startCode of a server,
057   * so any restarted instances of the same server will have different ServerName and will not
058   * coincide with past dead ones. So there's no need to cleanup this list.
059   */
060  private Set<ServerName> killedRegionServers = new HashSet<>();
061
062  public DistributedHBaseCluster(Configuration conf, ClusterManager clusterManager)
063      throws IOException {
064    super(conf);
065    this.clusterManager = clusterManager;
066    this.connection = ConnectionFactory.createConnection(conf);
067    this.admin = this.connection.getAdmin();
068    this.initialClusterStatus = getClusterMetrics();
069  }
070
071  public void setClusterManager(ClusterManager clusterManager) {
072    this.clusterManager = clusterManager;
073  }
074
075  public ClusterManager getClusterManager() {
076    return clusterManager;
077  }
078
079  /**
080   * Returns a ClusterStatus for this HBase cluster
081   * @throws IOException
082   */
083  @Override
084  public ClusterMetrics getClusterMetrics() throws IOException {
085    return admin.getClusterMetrics();
086  }
087
088  @Override
089  public ClusterMetrics getInitialClusterMetrics() throws IOException {
090    return initialClusterStatus;
091  }
092
093  @Override
094  public void close() throws IOException {
095    if (this.admin != null) {
096      admin.close();
097    }
098    if (this.connection != null && !this.connection.isClosed()) {
099      this.connection.close();
100    }
101  }
102
103  @Override
104  public AdminProtos.AdminService.BlockingInterface getAdminProtocol(ServerName serverName)
105  throws IOException {
106    return ((ClusterConnection)this.connection).getAdmin(serverName);
107  }
108
109  @Override
110  public ClientProtos.ClientService.BlockingInterface getClientProtocol(ServerName serverName)
111  throws IOException {
112    return ((ClusterConnection)this.connection).getClient(serverName);
113  }
114
115  @Override
116  public void startRegionServer(String hostname, int port) throws IOException {
117    LOG.info("Starting RS on: " + hostname);
118    clusterManager.start(ServiceType.HBASE_REGIONSERVER, hostname, port);
119  }
120
121  @Override
122  public void killRegionServer(ServerName serverName) throws IOException {
123    LOG.info("Aborting RS: " + serverName.getServerName());
124    killedRegionServers.add(serverName);
125    clusterManager.kill(ServiceType.HBASE_REGIONSERVER,
126      serverName.getHostname(), serverName.getPort());
127  }
128
129  @Override
130  public boolean isKilledRS(ServerName serverName) {
131    return killedRegionServers.contains(serverName);
132  }
133
134  @Override
135  public void stopRegionServer(ServerName serverName) throws IOException {
136    LOG.info("Stopping RS: " + serverName.getServerName());
137    clusterManager.stop(ServiceType.HBASE_REGIONSERVER,
138      serverName.getHostname(), serverName.getPort());
139  }
140
141  @Override
142  public void waitForRegionServerToStop(ServerName serverName, long timeout) throws IOException {
143    waitForServiceToStop(ServiceType.HBASE_REGIONSERVER, serverName, timeout);
144  }
145
146  @Override
147  public void startZkNode(String hostname, int port) throws IOException {
148    LOG.info("Starting ZooKeeper node on: " + hostname);
149    clusterManager.start(ServiceType.ZOOKEEPER_SERVER, hostname, port);
150  }
151
152  @Override
153  public void killZkNode(ServerName serverName) throws IOException {
154    LOG.info("Aborting ZooKeeper node on: " + serverName.getServerName());
155    clusterManager.kill(ServiceType.ZOOKEEPER_SERVER,
156      serverName.getHostname(), serverName.getPort());
157  }
158
159  @Override
160  public void stopZkNode(ServerName serverName) throws IOException {
161    LOG.info("Stopping ZooKeeper node: " + serverName.getServerName());
162    clusterManager.stop(ServiceType.ZOOKEEPER_SERVER,
163      serverName.getHostname(), serverName.getPort());
164  }
165
166  @Override
167  public void waitForZkNodeToStart(ServerName serverName, long timeout) throws IOException {
168    waitForServiceToStart(ServiceType.ZOOKEEPER_SERVER, serverName, timeout);
169  }
170
171  @Override
172  public void waitForZkNodeToStop(ServerName serverName, long timeout) throws IOException {
173    waitForServiceToStop(ServiceType.ZOOKEEPER_SERVER, serverName, timeout);
174  }
175
176  @Override
177  public void startDataNode(ServerName serverName) throws IOException {
178    LOG.info("Starting data node on: " + serverName.getServerName());
179    clusterManager.start(ServiceType.HADOOP_DATANODE,
180      serverName.getHostname(), serverName.getPort());
181  }
182
183  @Override
184  public void killDataNode(ServerName serverName) throws IOException {
185    LOG.info("Aborting data node on: " + serverName.getServerName());
186    clusterManager.kill(ServiceType.HADOOP_DATANODE,
187      serverName.getHostname(), serverName.getPort());
188  }
189
190  @Override
191  public void stopDataNode(ServerName serverName) throws IOException {
192    LOG.info("Stopping data node on: " + serverName.getServerName());
193    clusterManager.stop(ServiceType.HADOOP_DATANODE,
194      serverName.getHostname(), serverName.getPort());
195  }
196
197  @Override
198  public void waitForDataNodeToStart(ServerName serverName, long timeout) throws IOException {
199    waitForServiceToStart(ServiceType.HADOOP_DATANODE, serverName, timeout);
200  }
201
202  @Override
203  public void waitForDataNodeToStop(ServerName serverName, long timeout) throws IOException {
204    waitForServiceToStop(ServiceType.HADOOP_DATANODE, serverName, timeout);
205  }
206
207  private void waitForServiceToStop(ServiceType service, ServerName serverName, long timeout)
208    throws IOException {
209    LOG.info("Waiting for service: " + service + " to stop: " + serverName.getServerName());
210    long start = System.currentTimeMillis();
211
212    while ((System.currentTimeMillis() - start) < timeout) {
213      if (!clusterManager.isRunning(service, serverName.getHostname(), serverName.getPort())) {
214        return;
215      }
216      Threads.sleep(100);
217    }
218    throw new IOException("did timeout waiting for service to stop:" + serverName);
219  }
220
221  private void waitForServiceToStart(ServiceType service, ServerName serverName, long timeout)
222    throws IOException {
223    LOG.info("Waiting for service: " + service + " to start: " + serverName.getServerName());
224    long start = System.currentTimeMillis();
225
226    while ((System.currentTimeMillis() - start) < timeout) {
227      if (clusterManager.isRunning(service, serverName.getHostname(), serverName.getPort())) {
228        return;
229      }
230      Threads.sleep(100);
231    }
232    throw new IOException("did timeout waiting for service to start:" + serverName);
233  }
234
235
236  @Override
237  public MasterService.BlockingInterface getMasterAdminService()
238  throws IOException {
239    return ((ClusterConnection)this.connection).getMaster();
240  }
241
242  @Override
243  public void startMaster(String hostname, int port) throws IOException {
244    LOG.info("Starting Master on: " + hostname + ":" + port);
245    clusterManager.start(ServiceType.HBASE_MASTER, hostname, port);
246  }
247
248  @Override
249  public void killMaster(ServerName serverName) throws IOException {
250    LOG.info("Aborting Master: " + serverName.getServerName());
251    clusterManager.kill(ServiceType.HBASE_MASTER, serverName.getHostname(), serverName.getPort());
252  }
253
254  @Override
255  public void stopMaster(ServerName serverName) throws IOException {
256    LOG.info("Stopping Master: " + serverName.getServerName());
257    clusterManager.stop(ServiceType.HBASE_MASTER, serverName.getHostname(), serverName.getPort());
258  }
259
260  @Override
261  public void waitForMasterToStop(ServerName serverName, long timeout) throws IOException {
262    waitForServiceToStop(ServiceType.HBASE_MASTER, serverName, timeout);
263  }
264
265  @Override
266  public boolean waitForActiveAndReadyMaster(long timeout) throws IOException {
267    long start = System.currentTimeMillis();
268    while (System.currentTimeMillis() - start < timeout) {
269      try {
270        getMasterAdminService();
271        return true;
272      } catch (MasterNotRunningException m) {
273        LOG.warn("Master not started yet " + m);
274      } catch (ZooKeeperConnectionException e) {
275        LOG.warn("Failed to connect to ZK " + e);
276      }
277      Threads.sleep(1000);
278    }
279    return false;
280  }
281
282  @Override
283  public ServerName getServerHoldingRegion(TableName tn, byte[] regionName) throws IOException {
284    HRegionLocation regionLoc = null;
285    try (RegionLocator locator = connection.getRegionLocator(tn)) {
286      regionLoc = locator.getRegionLocation(regionName, true);
287    }
288    if (regionLoc == null) {
289      LOG.warn("Cannot find region server holding region " + Bytes.toString(regionName) +
290        ", start key [" + Bytes.toString(HRegionInfo.getStartKey(regionName)) + "]");
291      return null;
292    }
293
294    AdminProtos.AdminService.BlockingInterface client =
295        ((ClusterConnection)this.connection).getAdmin(regionLoc.getServerName());
296    ServerInfo info = ProtobufUtil.getServerInfo(null, client);
297    return ProtobufUtil.toServerName(info.getServerName());
298  }
299
300  @Override
301  public void waitUntilShutDown() {
302    // Simply wait for a few seconds for now (after issuing serverManager.kill
303    throw new RuntimeException(HConstants.NOT_IMPLEMENTED);
304  }
305
306  @Override
307  public void shutdown() throws IOException {
308    // not sure we want this
309    throw new RuntimeException(HConstants.NOT_IMPLEMENTED);
310  }
311
312  @Override
313  public boolean isDistributedCluster() {
314    return true;
315  }
316
317  @Override
318  public boolean restoreClusterMetrics(ClusterMetrics initial) throws IOException {
319    ClusterMetrics current = getClusterMetrics();
320
321    LOG.info("Restoring cluster - started");
322
323    // do a best effort restore
324    boolean success = true;
325    success = restoreMasters(initial, current) & success;
326    success = restoreRegionServers(initial, current) & success;
327    success = restoreAdmin() & success;
328
329    LOG.info("Restoring cluster - done");
330    return success;
331  }
332
333  protected boolean restoreMasters(ClusterMetrics initial, ClusterMetrics current) {
334    List<IOException> deferred = new ArrayList<>();
335    //check whether current master has changed
336    final ServerName initMaster = initial.getMasterName();
337    if (!ServerName.isSameAddress(initMaster, current.getMasterName())) {
338      LOG.info("Restoring cluster - Initial active master : "
339              + initMaster.getHostAndPort()
340              + " has changed to : "
341              + current.getMasterName().getHostAndPort());
342      // If initial master is stopped, start it, before restoring the state.
343      // It will come up as a backup master, if there is already an active master.
344      try {
345        if (!clusterManager.isRunning(ServiceType.HBASE_MASTER,
346                initMaster.getHostname(), initMaster.getPort())) {
347          LOG.info("Restoring cluster - starting initial active master at:"
348                  + initMaster.getHostAndPort());
349          startMaster(initMaster.getHostname(), initMaster.getPort());
350        }
351
352        // master has changed, we would like to undo this.
353        // 1. Kill the current backups
354        // 2. Stop current master
355        // 3. Start backup masters
356        for (ServerName currentBackup : current.getBackupMasterNames()) {
357          if (!ServerName.isSameAddress(currentBackup, initMaster)) {
358            LOG.info("Restoring cluster - stopping backup master: " + currentBackup);
359            stopMaster(currentBackup);
360          }
361        }
362        LOG.info("Restoring cluster - stopping active master: " + current.getMasterName());
363        stopMaster(current.getMasterName());
364        waitForActiveAndReadyMaster(); // wait so that active master takes over
365      } catch (IOException ex) {
366        // if we fail to start the initial active master, we do not want to continue stopping
367        // backup masters. Just keep what we have now
368        deferred.add(ex);
369      }
370
371      //start backup masters
372      for (ServerName backup : initial.getBackupMasterNames()) {
373        try {
374          //these are not started in backup mode, but we should already have an active master
375          if (!clusterManager.isRunning(ServiceType.HBASE_MASTER,
376                  backup.getHostname(),
377                  backup.getPort())) {
378            LOG.info("Restoring cluster - starting initial backup master: "
379                    + backup.getHostAndPort());
380            startMaster(backup.getHostname(), backup.getPort());
381          }
382        } catch (IOException ex) {
383          deferred.add(ex);
384        }
385      }
386    } else {
387      //current master has not changed, match up backup masters
388      Set<ServerName> toStart = new TreeSet<>(new ServerNameIgnoreStartCodeComparator());
389      Set<ServerName> toKill = new TreeSet<>(new ServerNameIgnoreStartCodeComparator());
390      toStart.addAll(initial.getBackupMasterNames());
391      toKill.addAll(current.getBackupMasterNames());
392
393      for (ServerName server : current.getBackupMasterNames()) {
394        toStart.remove(server);
395      }
396      for (ServerName server: initial.getBackupMasterNames()) {
397        toKill.remove(server);
398      }
399
400      for (ServerName sn:toStart) {
401        try {
402          if(!clusterManager.isRunning(ServiceType.HBASE_MASTER, sn.getHostname(), sn.getPort())) {
403            LOG.info("Restoring cluster - starting initial backup master: " + sn.getHostAndPort());
404            startMaster(sn.getHostname(), sn.getPort());
405          }
406        } catch (IOException ex) {
407          deferred.add(ex);
408        }
409      }
410
411      for (ServerName sn:toKill) {
412        try {
413          if(clusterManager.isRunning(ServiceType.HBASE_MASTER, sn.getHostname(), sn.getPort())) {
414            LOG.info("Restoring cluster - stopping backup master: " + sn.getHostAndPort());
415            stopMaster(sn);
416          }
417        } catch (IOException ex) {
418          deferred.add(ex);
419        }
420      }
421    }
422    if (!deferred.isEmpty()) {
423      LOG.warn("Restoring cluster - restoring region servers reported "
424              + deferred.size() + " errors:");
425      for (int i=0; i<deferred.size() && i < 3; i++) {
426        LOG.warn(Objects.toString(deferred.get(i)));
427      }
428    }
429
430    return deferred.isEmpty();
431  }
432
433
434  private static class ServerNameIgnoreStartCodeComparator implements Comparator<ServerName> {
435    @Override
436    public int compare(ServerName o1, ServerName o2) {
437      int compare = o1.getHostname().compareToIgnoreCase(o2.getHostname());
438      if (compare != 0) return compare;
439      compare = o1.getPort() - o2.getPort();
440      if (compare != 0) return compare;
441      return 0;
442    }
443  }
444
445  protected boolean restoreRegionServers(ClusterMetrics initial, ClusterMetrics current) {
446    Set<ServerName> toStart = new TreeSet<>(new ServerNameIgnoreStartCodeComparator());
447    Set<ServerName> toKill = new TreeSet<>(new ServerNameIgnoreStartCodeComparator());
448    toStart.addAll(initial.getLiveServerMetrics().keySet());
449    toKill.addAll(current.getLiveServerMetrics().keySet());
450
451    ServerName master = initial.getMasterName();
452
453    for (ServerName server : current.getLiveServerMetrics().keySet()) {
454      toStart.remove(server);
455    }
456    for (ServerName server: initial.getLiveServerMetrics().keySet()) {
457      toKill.remove(server);
458    }
459
460    List<IOException> deferred = new ArrayList<>();
461
462    for(ServerName sn:toStart) {
463      try {
464        if (!clusterManager.isRunning(ServiceType.HBASE_REGIONSERVER,
465                sn.getHostname(),
466                sn.getPort())
467                && master.getPort() != sn.getPort()) {
468          LOG.info("Restoring cluster - starting initial region server: " + sn.getHostAndPort());
469          startRegionServer(sn.getHostname(), sn.getPort());
470        }
471      } catch (IOException ex) {
472        deferred.add(ex);
473      }
474    }
475
476    for(ServerName sn:toKill) {
477      try {
478        if (clusterManager.isRunning(ServiceType.HBASE_REGIONSERVER,
479                sn.getHostname(),
480                sn.getPort())
481                && master.getPort() != sn.getPort()){
482          LOG.info("Restoring cluster - stopping initial region server: " + sn.getHostAndPort());
483          stopRegionServer(sn);
484        }
485      } catch (IOException ex) {
486        deferred.add(ex);
487      }
488    }
489    if (!deferred.isEmpty()) {
490      LOG.warn("Restoring cluster - restoring region servers reported "
491              + deferred.size() + " errors:");
492      for (int i=0; i<deferred.size() && i < 3; i++) {
493        LOG.warn(Objects.toString(deferred.get(i)));
494      }
495    }
496
497    return deferred.isEmpty();
498  }
499
500  protected boolean restoreAdmin() throws IOException {
501    // While restoring above, if the HBase Master which was initially the Active one, was down
502    // and the restore put the cluster back to Initial configuration, HAdmin instance will need
503    // to refresh its connections (otherwise it will return incorrect information) or we can
504    // point it to new instance.
505    try {
506      admin.close();
507    } catch (IOException ioe) {
508      LOG.warn("While closing the old connection", ioe);
509    }
510    this.admin = this.connection.getAdmin();
511    LOG.info("Added new HBaseAdmin");
512    return true;
513  }
514}