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}