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.util; 019 020import java.io.BufferedInputStream; 021import java.io.BufferedOutputStream; 022import java.io.Closeable; 023import java.io.DataInputStream; 024import java.io.DataOutputStream; 025import java.io.File; 026import java.io.FileInputStream; 027import java.io.FileOutputStream; 028import java.io.IOException; 029import java.net.InetAddress; 030import java.nio.file.Files; 031import java.nio.file.Paths; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Collection; 035import java.util.Collections; 036import java.util.EnumSet; 037import java.util.HashSet; 038import java.util.Iterator; 039import java.util.List; 040import java.util.Locale; 041import java.util.Optional; 042import java.util.Set; 043import java.util.concurrent.Callable; 044import java.util.concurrent.CancellationException; 045import java.util.concurrent.ExecutionException; 046import java.util.concurrent.ExecutorService; 047import java.util.concurrent.Executors; 048import java.util.concurrent.Future; 049import java.util.concurrent.TimeUnit; 050import java.util.concurrent.TimeoutException; 051import java.util.function.Predicate; 052import org.apache.commons.io.IOUtils; 053import org.apache.hadoop.conf.Configuration; 054import org.apache.hadoop.hbase.ClusterMetrics.Option; 055import org.apache.hadoop.hbase.HBaseConfiguration; 056import org.apache.hadoop.hbase.HConstants; 057import org.apache.hadoop.hbase.HRegionLocation; 058import org.apache.hadoop.hbase.MetaTableAccessor; 059import org.apache.hadoop.hbase.ServerName; 060import org.apache.hadoop.hbase.UnknownRegionException; 061import org.apache.hadoop.hbase.client.Admin; 062import org.apache.hadoop.hbase.client.Connection; 063import org.apache.hadoop.hbase.client.ConnectionFactory; 064import org.apache.hadoop.hbase.client.DoNotRetryRegionException; 065import org.apache.hadoop.hbase.client.RegionInfo; 066import org.apache.hadoop.hbase.client.RegionInfoBuilder; 067import org.apache.hadoop.hbase.client.Result; 068import org.apache.hadoop.hbase.master.RackManager; 069import org.apache.hadoop.hbase.master.RegionState; 070import org.apache.hadoop.hbase.master.assignment.AssignmentManager; 071import org.apache.hadoop.hbase.net.Address; 072import org.apache.hadoop.hbase.rsgroup.RSGroupInfo; 073import org.apache.hadoop.hbase.zookeeper.MetaTableLocator; 074import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 075import org.apache.hadoop.hbase.zookeeper.ZNodePaths; 076import org.apache.yetus.audience.InterfaceAudience; 077import org.slf4j.Logger; 078import org.slf4j.LoggerFactory; 079 080import org.apache.hbase.thirdparty.com.google.common.net.InetAddresses; 081import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 082import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils; 083 084/** 085 * Tool for loading/unloading regions to/from given regionserver This tool can be run from Command 086 * line directly as a utility. Supports Ack/No Ack mode for loading/unloading operations.Ack mode 087 * acknowledges if regions are online after movement while noAck mode is best effort mode that 088 * improves performance but will still move on if region is stuck/not moved. Motivation behind noAck 089 * mode being RS shutdown where even if a Region is stuck, upon shutdown master will move it 090 * anyways. This can also be used by constructiong an Object using the builder and then calling 091 * {@link #load()} or {@link #unload()} methods for the desired operations. 092 */ 093@InterfaceAudience.Public 094public class RegionMover extends AbstractHBaseTool implements Closeable { 095 public static final String MOVE_RETRIES_MAX_KEY = "hbase.move.retries.max"; 096 public static final String MOVE_WAIT_MAX_KEY = "hbase.move.wait.max"; 097 public static final String SERVERSTART_WAIT_MAX_KEY = "hbase.serverstart.wait.max"; 098 public static final int DEFAULT_MOVE_RETRIES_MAX = 5; 099 public static final int DEFAULT_MOVE_WAIT_MAX = 60; 100 public static final int DEFAULT_SERVERSTART_WAIT_MAX = 180; 101 102 private static final Logger LOG = LoggerFactory.getLogger(RegionMover.class); 103 104 private RegionMoverBuilder rmbuilder; 105 private boolean ack = true; 106 private int maxthreads = 1; 107 private int timeout; 108 private List<String> isolateRegionIdArray; 109 private String loadUnload; 110 private String hostname; 111 private String filename; 112 private String excludeFile; 113 private String designatedFile; 114 private int port; 115 private Connection conn; 116 private Admin admin; 117 private RackManager rackManager; 118 119 private RegionMover(RegionMoverBuilder builder) throws IOException { 120 this.hostname = builder.hostname; 121 this.filename = builder.filename; 122 this.excludeFile = builder.excludeFile; 123 this.designatedFile = builder.designatedFile; 124 this.maxthreads = builder.maxthreads; 125 this.isolateRegionIdArray = builder.isolateRegionIdArray; 126 this.ack = builder.ack; 127 this.port = builder.port; 128 this.timeout = builder.timeout; 129 setConf(builder.conf); 130 this.conn = ConnectionFactory.createConnection(conf); 131 this.admin = conn.getAdmin(); 132 133 // if the hostname of master is ip, it indicates that the master/RS has enabled use-ip, we need 134 // to resolve the current hostname to ip to ensure that the RegionMover logic can be executed 135 // normally, see HBASE-27304 for details. 136 ServerName master = admin.getClusterMetrics(EnumSet.of(Option.MASTER)).getMasterName(); 137 if (InetAddresses.isInetAddress(master.getHostname())) { 138 if (!InetAddresses.isInetAddress(this.hostname)) { 139 this.hostname = InetAddress.getByName(this.hostname).getHostAddress(); 140 } 141 } 142 143 // Only while running unit tests, builder.rackManager will not be null for the convenience of 144 // providing custom rackManager. Otherwise for regular workflow/user triggered action, 145 // builder.rackManager is supposed to be null. Hence, setter of builder.rackManager is 146 // provided as @InterfaceAudience.Private and it is commented that this is just 147 // to be used by unit test. 148 rackManager = builder.rackManager == null ? new RackManager(conf) : builder.rackManager; 149 } 150 151 private RegionMover() { 152 } 153 154 @Override 155 public void close() { 156 IOUtils.closeQuietly(this.admin, e -> LOG.warn("failed to close admin", e)); 157 IOUtils.closeQuietly(this.conn, e -> LOG.warn("failed to close conn", e)); 158 } 159 160 /** 161 * Builder for Region mover. Use the {@link #build()} method to create RegionMover object. Has 162 * {@link #filename(String)}, {@link #excludeFile(String)}, {@link #maxthreads(int)}, 163 * {@link #ack(boolean)}, {@link #timeout(int)}, {@link #designatedFile(String)} methods to set 164 * the corresponding options. 165 */ 166 public static class RegionMoverBuilder { 167 private boolean ack = true; 168 private int maxthreads = 1; 169 private int timeout = Integer.MAX_VALUE; 170 private List<String> isolateRegionIdArray = new ArrayList<>(); 171 private String hostname; 172 private String filename; 173 private String excludeFile = null; 174 private String designatedFile = null; 175 private String defaultDir = System.getProperty("java.io.tmpdir"); 176 @InterfaceAudience.Private 177 final int port; 178 private final Configuration conf; 179 private RackManager rackManager; 180 181 public RegionMoverBuilder(String hostname) { 182 this(hostname, createConf()); 183 } 184 185 /** 186 * Creates a new configuration and sets region mover specific overrides 187 */ 188 private static Configuration createConf() { 189 final Configuration conf = HBaseConfiguration.create(); 190 return overrideConf(conf); 191 } 192 193 private static Configuration overrideConf(Configuration conf) { 194 conf.setInt("hbase.client.prefetch.limit", 1); 195 conf.setInt("hbase.client.pause", 500); 196 conf.setInt("hbase.client.retries.number", 100); 197 return conf; 198 } 199 200 /** 201 * @param hostname Hostname to unload regions from or load regions to. Can be either hostname or 202 * hostname:port. 203 * @param conf Configuration object 204 */ 205 public RegionMoverBuilder(String hostname, Configuration conf) { 206 String[] splitHostname = hostname.toLowerCase().split(":"); 207 this.hostname = splitHostname[0]; 208 if (splitHostname.length == 2) { 209 this.port = Integer.parseInt(splitHostname[1]); 210 } else { 211 this.port = conf.getInt(HConstants.REGIONSERVER_PORT, HConstants.DEFAULT_REGIONSERVER_PORT); 212 } 213 this.filename = defaultDir + File.separator + System.getProperty("user.name") + this.hostname 214 + ":" + Integer.toString(this.port); 215 this.conf = overrideConf(new Configuration(conf)); 216 } 217 218 /** 219 * Path of file where regions will be written to during unloading/read from during loading 220 * @return RegionMoverBuilder object 221 */ 222 public RegionMoverBuilder filename(String filename) { 223 this.filename = filename; 224 return this; 225 } 226 227 /** 228 * Set the max number of threads that will be used to move regions 229 */ 230 public RegionMoverBuilder maxthreads(int threads) { 231 this.maxthreads = threads; 232 return this; 233 } 234 235 /** 236 * Set the region ID to isolate on the region server. 237 */ 238 public RegionMoverBuilder isolateRegionIdArray(List<String> isolateRegionIdArray) { 239 this.isolateRegionIdArray = isolateRegionIdArray; 240 return this; 241 } 242 243 /** 244 * Path of file containing hostnames to be excluded during region movement. Exclude file should 245 * have 'host:port' per line. Port is mandatory here as we can have many RS running on a single 246 * host. 247 */ 248 public RegionMoverBuilder excludeFile(String excludefile) { 249 this.excludeFile = excludefile; 250 return this; 251 } 252 253 /** 254 * Set the designated file. Designated file contains hostnames where region moves. Designated 255 * file should have 'host:port' per line. Port is mandatory here as we can have many RS running 256 * on a single host. 257 * @param designatedFile The designated file 258 * @return RegionMoverBuilder object 259 */ 260 public RegionMoverBuilder designatedFile(String designatedFile) { 261 this.designatedFile = designatedFile; 262 return this; 263 } 264 265 /** 266 * Set ack/noAck mode. 267 * <p> 268 * In ack mode regions are acknowledged before and after moving and the move is retried 269 * hbase.move.retries.max times, if unsuccessful we quit with exit code 1.No Ack mode is a best 270 * effort mode,each region movement is tried once.This can be used during graceful shutdown as 271 * even if we have a stuck region,upon shutdown it'll be reassigned anyway. 272 * <p> 273 * @return RegionMoverBuilder object 274 */ 275 public RegionMoverBuilder ack(boolean ack) { 276 this.ack = ack; 277 return this; 278 } 279 280 /** 281 * Set the timeout for Load/Unload operation in seconds.This is a global timeout,threadpool for 282 * movers also have a separate time which is hbase.move.wait.max * number of regions to 283 * load/unload 284 * @param timeout in seconds 285 * @return RegionMoverBuilder object 286 */ 287 public RegionMoverBuilder timeout(int timeout) { 288 this.timeout = timeout; 289 return this; 290 } 291 292 /** 293 * Set specific rackManager implementation. This setter method is for testing purpose only. 294 * @param rackManager rackManager impl 295 * @return RegionMoverBuilder object 296 */ 297 @InterfaceAudience.Private 298 public RegionMoverBuilder rackManager(RackManager rackManager) { 299 this.rackManager = rackManager; 300 return this; 301 } 302 303 /** 304 * This method builds the appropriate RegionMover object which can then be used to load/unload 305 * using load and unload methods 306 * @return RegionMover object 307 */ 308 public RegionMover build() throws IOException { 309 return new RegionMover(this); 310 } 311 } 312 313 /** 314 * Loads the specified {@link #hostname} with regions listed in the {@link #filename} RegionMover 315 * Object has to be created using {@link #RegionMover(RegionMoverBuilder)} 316 * @return true if loading succeeded, false otherwise 317 */ 318 public boolean load() throws ExecutionException, InterruptedException, TimeoutException { 319 ExecutorService loadPool = Executors.newFixedThreadPool(1); 320 Future<Boolean> loadTask = loadPool.submit(getMetaRegionMovePlan()); 321 boolean isMetaMoved = waitTaskToFinish(loadPool, loadTask, "loading"); 322 if (!isMetaMoved) { 323 return false; 324 } 325 loadPool = Executors.newFixedThreadPool(1); 326 loadTask = loadPool.submit(getNonMetaRegionsMovePlan()); 327 return waitTaskToFinish(loadPool, loadTask, "loading"); 328 } 329 330 private Callable<Boolean> getMetaRegionMovePlan() { 331 return getRegionsMovePlan(true); 332 } 333 334 private Callable<Boolean> getNonMetaRegionsMovePlan() { 335 return getRegionsMovePlan(false); 336 } 337 338 private Callable<Boolean> getRegionsMovePlan(boolean moveMetaRegion) { 339 return () -> { 340 try { 341 List<RegionInfo> regionsToMove = readRegionsFromFile(filename); 342 if (regionsToMove.isEmpty()) { 343 LOG.info("No regions to load.Exiting"); 344 return true; 345 } 346 Optional<RegionInfo> metaRegion = getMetaRegionInfoIfToBeMoved(regionsToMove); 347 if (moveMetaRegion) { 348 if (metaRegion.isPresent()) { 349 loadRegions(Collections.singletonList(metaRegion.get())); 350 } 351 } else { 352 metaRegion.ifPresent(regionsToMove::remove); 353 loadRegions(regionsToMove); 354 } 355 } catch (Exception e) { 356 LOG.error("Error while loading regions to " + hostname, e); 357 return false; 358 } 359 return true; 360 }; 361 } 362 363 private Optional<RegionInfo> getMetaRegionInfoIfToBeMoved(List<RegionInfo> regionsToMove) { 364 return regionsToMove.stream().filter(RegionInfo::isMetaRegion).findFirst(); 365 } 366 367 private void loadRegions(List<RegionInfo> regionsToMove) throws Exception { 368 ServerName server = getTargetServer(); 369 List<RegionInfo> movedRegions = Collections.synchronizedList(new ArrayList<>()); 370 LOG.info("Moving " + regionsToMove.size() + " regions to " + server + " using " 371 + this.maxthreads + " threads.Ack mode:" + this.ack); 372 373 final ExecutorService moveRegionsPool = Executors.newFixedThreadPool(this.maxthreads); 374 List<Future<Boolean>> taskList = new ArrayList<>(); 375 int counter = 0; 376 while (counter < regionsToMove.size()) { 377 RegionInfo region = regionsToMove.get(counter); 378 ServerName currentServer = MoveWithAck.getServerNameForRegion(region, admin, conn); 379 if (currentServer == null) { 380 LOG 381 .warn("Could not get server for Region:" + region.getRegionNameAsString() + " moving on"); 382 counter++; 383 continue; 384 } else if (server.equals(currentServer)) { 385 LOG.info( 386 "Region " + region.getRegionNameAsString() + " is already on target server=" + server); 387 counter++; 388 continue; 389 } 390 if (ack) { 391 Future<Boolean> task = moveRegionsPool 392 .submit(new MoveWithAck(conn, region, currentServer, server, movedRegions)); 393 taskList.add(task); 394 } else { 395 Future<Boolean> task = moveRegionsPool 396 .submit(new MoveWithoutAck(admin, region, currentServer, server, movedRegions)); 397 taskList.add(task); 398 } 399 counter++; 400 } 401 402 moveRegionsPool.shutdown(); 403 long timeoutInSeconds = regionsToMove.size() 404 * admin.getConfiguration().getLong(MOVE_WAIT_MAX_KEY, DEFAULT_MOVE_WAIT_MAX); 405 waitMoveTasksToFinish(moveRegionsPool, taskList, timeoutInSeconds); 406 } 407 408 /** 409 * Unload regions from given {@link #hostname} using ack/noAck mode and {@link #maxthreads}.In 410 * noAck mode we do not make sure that region is successfully online on the target region 411 * server,hence it is best effort.We do not unload regions to hostnames given in 412 * {@link #excludeFile}. If designatedFile is present with some contents, we will unload regions 413 * to hostnames provided in {@link #designatedFile} 414 * @return true if unloading succeeded, false otherwise 415 */ 416 public boolean unload() throws InterruptedException, ExecutionException, TimeoutException { 417 return unloadRegions(false); 418 } 419 420 /** 421 * Unload regions from given {@link #hostname} using ack/noAck mode and {@link #maxthreads}.In 422 * noAck mode we do not make sure that region is successfully online on the target region 423 * server,hence it is best effort.We do not unload regions to hostnames given in 424 * {@link #excludeFile}. If designatedFile is present with some contents, we will unload regions 425 * to hostnames provided in {@link #designatedFile}. While unloading regions, destination 426 * RegionServers are selected from different rack i.e regions should not move to any RegionServers 427 * that belong to same rack as source RegionServer. 428 * @return true if unloading succeeded, false otherwise 429 */ 430 public boolean unloadFromRack() 431 throws InterruptedException, ExecutionException, TimeoutException { 432 return unloadRegions(true); 433 } 434 435 private boolean unloadRegions(boolean unloadFromRack) 436 throws ExecutionException, InterruptedException, TimeoutException { 437 return unloadRegions(unloadFromRack, null); 438 } 439 440 /** 441 * Isolated regions specified in {@link #isolateRegionIdArray} on {@link #hostname} in ack Mode 442 * and Unload regions from given {@link #hostname} using ack/noAck mode and {@link #maxthreads}. 443 * In noAck mode we do not make sure that region is successfully online on the target region 444 * server,hence it is the best effort. We do not unload regions to hostnames given in 445 * {@link #excludeFile}. If designatedFile is present with some contents, we will unload regions 446 * to hostnames provided in {@link #designatedFile} 447 * @return true if region isolation succeeded, false otherwise 448 */ 449 public boolean isolateRegions() 450 throws ExecutionException, InterruptedException, TimeoutException { 451 return unloadRegions(false, isolateRegionIdArray); 452 } 453 454 private boolean unloadRegions(boolean unloadFromRack, List<String> isolateRegionIdArray) 455 throws InterruptedException, ExecutionException, TimeoutException { 456 deleteFile(this.filename); 457 ExecutorService unloadPool = Executors.newFixedThreadPool(1); 458 Future<Boolean> unloadTask = unloadPool.submit(() -> { 459 List<RegionInfo> movedRegions = Collections.synchronizedList(new ArrayList<>()); 460 try { 461 // Get Online RegionServers 462 List<ServerName> regionServers = new ArrayList<>(); 463 RSGroupInfo rsgroup = admin.getRSGroup(Address.fromParts(hostname, port)); 464 LOG.info("{} belongs to {}", hostname, rsgroup.getName()); 465 regionServers.addAll(filterRSGroupServers(rsgroup, admin.getRegionServers())); 466 // Remove the host Region server from target Region Servers list 467 ServerName server = stripServer(regionServers, hostname, port); 468 if (server == null) { 469 LOG.info("Could not find server '{}:{}' in the set of region servers. giving up.", 470 hostname, port); 471 LOG.debug("List of region servers: {}", regionServers); 472 return false; 473 } 474 // Remove RS not present in the designated file 475 includeExcludeRegionServers(designatedFile, regionServers, true); 476 477 // Remove RS present in the exclude file 478 includeExcludeRegionServers(excludeFile, regionServers, false); 479 480 if (unloadFromRack) { 481 // remove regionServers that belong to same rack (as source host) since the goal is to 482 // unload regions from source regionServer to destination regionServers 483 // that belong to different rack only. 484 String sourceRack = rackManager.getRack(server); 485 List<String> racks = rackManager.getRack(regionServers); 486 Iterator<ServerName> iterator = regionServers.iterator(); 487 int i = 0; 488 while (iterator.hasNext()) { 489 iterator.next(); 490 if (racks.size() > i && racks.get(i) != null && racks.get(i).equals(sourceRack)) { 491 iterator.remove(); 492 } 493 i++; 494 } 495 } 496 497 // Remove decommissioned RS 498 Set<ServerName> decommissionedRS = new HashSet<>(admin.listDecommissionedRegionServers()); 499 if (CollectionUtils.isNotEmpty(decommissionedRS)) { 500 regionServers.removeIf(decommissionedRS::contains); 501 LOG.debug("Excluded RegionServers from unloading regions to because they " 502 + "are marked as decommissioned. Servers: {}", decommissionedRS); 503 } 504 505 stripMaster(regionServers); 506 if (regionServers.isEmpty()) { 507 LOG.warn("No Regions were moved - no servers available"); 508 return false; 509 } else { 510 LOG.info("Available servers {}", regionServers); 511 } 512 unloadRegions(server, regionServers, movedRegions, isolateRegionIdArray); 513 } catch (Exception e) { 514 LOG.error("Error while unloading regions ", e); 515 return false; 516 } finally { 517 if (movedRegions != null) { 518 writeFile(filename, movedRegions); 519 } 520 } 521 return true; 522 }); 523 return waitTaskToFinish(unloadPool, unloadTask, "unloading"); 524 } 525 526 @InterfaceAudience.Private 527 Collection<ServerName> filterRSGroupServers(RSGroupInfo rsgroup, 528 Collection<ServerName> onlineServers) { 529 if (rsgroup.getName().equals(RSGroupInfo.DEFAULT_GROUP)) { 530 return onlineServers; 531 } 532 List<ServerName> serverLists = new ArrayList<>(rsgroup.getServers().size()); 533 for (ServerName server : onlineServers) { 534 Address address = Address.fromParts(server.getHostname(), server.getPort()); 535 if (rsgroup.containsServer(address)) { 536 serverLists.add(server); 537 } 538 } 539 return serverLists; 540 } 541 542 private void unloadRegions(ServerName server, List<ServerName> regionServers, 543 List<RegionInfo> movedRegions, List<String> isolateRegionIdArray) throws Exception { 544 while (true) { 545 List<RegionInfo> isolateRegionInfoList = Collections.synchronizedList(new ArrayList<>()); 546 RegionInfo isolateRegionInfo = null; 547 if (isolateRegionIdArray != null && !isolateRegionIdArray.isEmpty()) { 548 // Region will be moved to target region server with Ack mode. 549 final ExecutorService isolateRegionPool = Executors.newFixedThreadPool(maxthreads); 550 List<Future<Boolean>> isolateRegionTaskList = new ArrayList<>(); 551 List<RegionInfo> recentlyIsolatedRegion = Collections.synchronizedList(new ArrayList<>()); 552 boolean allRegionOpsSuccessful = true; 553 boolean isMetaIsolated = false; 554 RegionInfo metaRegionInfo = RegionInfoBuilder.FIRST_META_REGIONINFO; 555 List<HRegionLocation> hRegionLocationRegionIsolation = 556 Collections.synchronizedList(new ArrayList<>()); 557 for (String isolateRegionId : isolateRegionIdArray) { 558 if (isolateRegionId.equalsIgnoreCase(metaRegionInfo.getEncodedName())) { 559 isMetaIsolated = true; 560 continue; 561 } 562 Result result = MetaTableAccessor.scanByRegionEncodedName(conn, isolateRegionId); 563 HRegionLocation hRegionLocation = 564 MetaTableAccessor.getRegionLocation(conn, result.getRow()); 565 if (hRegionLocation != null) { 566 hRegionLocationRegionIsolation.add(hRegionLocation); 567 } else { 568 LOG.error("Region " + isolateRegionId + " doesn't exists/can't fetch from" 569 + " meta...Quitting now"); 570 // We only move the regions if all the regions were found. 571 allRegionOpsSuccessful = false; 572 break; 573 } 574 } 575 576 if (!allRegionOpsSuccessful) { 577 break; 578 } 579 // If hbase:meta region was isolated, then it needs to be part of isolateRegionInfoList. 580 if (isMetaIsolated) { 581 ZKWatcher zkWatcher = new ZKWatcher(conf, null, null); 582 List<HRegionLocation> result = new ArrayList<>(); 583 for (String znode : zkWatcher.getMetaReplicaNodes()) { 584 String path = ZNodePaths.joinZNode(zkWatcher.getZNodePaths().baseZNode, znode); 585 int replicaId = zkWatcher.getZNodePaths().getMetaReplicaIdFromPath(path); 586 RegionState state = MetaTableLocator.getMetaRegionState(zkWatcher, replicaId); 587 result.add(new HRegionLocation(state.getRegion(), state.getServerName())); 588 } 589 ServerName metaSeverName = result.get(0).getServerName(); 590 // For isolating hbase:meta, it should move explicitly in Ack mode, 591 // hence the forceMoveRegionByAck = true. 592 if (!metaSeverName.equals(server)) { 593 LOG.info("Region of hbase:meta " + metaRegionInfo.getEncodedName() + " is on server " 594 + metaSeverName + " moving to " + server); 595 submitRegionMovesWhileUnloading(metaSeverName, Collections.singletonList(server), 596 movedRegions, Collections.singletonList(metaRegionInfo), true); 597 } else { 598 LOG.info("Region of hbase:meta " + metaRegionInfo.getEncodedName() + " already exists" 599 + " on server : " + server); 600 } 601 isolateRegionInfoList.add(RegionInfoBuilder.FIRST_META_REGIONINFO); 602 } 603 604 if (!hRegionLocationRegionIsolation.isEmpty()) { 605 for (HRegionLocation hRegionLocation : hRegionLocationRegionIsolation) { 606 isolateRegionInfo = hRegionLocation.getRegion(); 607 isolateRegionInfoList.add(isolateRegionInfo); 608 if (hRegionLocation.getServerName() == server) { 609 LOG.info("Region " + hRegionLocation.getRegion().getEncodedName() + " already exists" 610 + " on server : " + server.getHostname()); 611 } else { 612 Future<Boolean> isolateRegionTask = 613 isolateRegionPool.submit(new MoveWithAck(conn, isolateRegionInfo, 614 hRegionLocation.getServerName(), server, recentlyIsolatedRegion)); 615 isolateRegionTaskList.add(isolateRegionTask); 616 } 617 } 618 } 619 620 if (!isolateRegionTaskList.isEmpty()) { 621 isolateRegionPool.shutdown(); 622 // Now that we have fetched all the region's regionInfo, we can move them. 623 waitMoveTasksToFinish(isolateRegionPool, isolateRegionTaskList, 624 admin.getConfiguration().getLong(MOVE_WAIT_MAX_KEY, DEFAULT_MOVE_WAIT_MAX)); 625 626 Set<RegionInfo> currentRegionsOnTheServer = new HashSet<>(admin.getRegions(server)); 627 if (!currentRegionsOnTheServer.containsAll(isolateRegionInfoList)) { 628 // If all the regions are not online on the target server, 629 // we don't put RS in decommission mode and exit from here. 630 LOG.error("One of the Region move failed OR stuck in transition...Quitting now"); 631 break; 632 } 633 } else { 634 LOG.info("All regions already exists on server : " + server.getHostname()); 635 } 636 // Once region has been moved to target RS, put the target RS into decommission mode, 637 // so master doesn't assign new region to the target RS while we unload the target RS. 638 // Also pass 'offload' flag as false since we don't want master to offload the target RS. 639 List<ServerName> listOfServer = new ArrayList<>(); 640 listOfServer.add(server); 641 LOG.info("Putting server : " + server.getHostname() + " in decommission/draining mode"); 642 admin.decommissionRegionServers(listOfServer, false); 643 } 644 List<RegionInfo> regionsToMove = admin.getRegions(server); 645 // Remove all the regions from the online Region list, that we just isolated. 646 // This will also include hbase:meta if it was isolated. 647 regionsToMove.removeAll(isolateRegionInfoList); 648 regionsToMove.removeAll(movedRegions); 649 if (regionsToMove.isEmpty()) { 650 LOG.info("No Regions to move....Quitting now"); 651 break; 652 } 653 LOG.info("Moving {} regions from {} to {} servers using {} threads .Ack Mode: {}", 654 regionsToMove.size(), this.hostname, regionServers.size(), this.maxthreads, ack); 655 656 Optional<RegionInfo> metaRegion = getMetaRegionInfoIfToBeMoved(regionsToMove); 657 if (metaRegion.isPresent()) { 658 RegionInfo meta = metaRegion.get(); 659 // hbase:meta should move explicitly in Ack mode. 660 submitRegionMovesWhileUnloading(server, regionServers, movedRegions, 661 Collections.singletonList(meta), true); 662 regionsToMove.remove(meta); 663 } 664 submitRegionMovesWhileUnloading(server, regionServers, movedRegions, regionsToMove, false); 665 } 666 } 667 668 private void submitRegionMovesWhileUnloading(ServerName server, List<ServerName> regionServers, 669 List<RegionInfo> movedRegions, List<RegionInfo> regionsToMove, boolean forceMoveRegionByAck) 670 throws Exception { 671 final ExecutorService moveRegionsPool = Executors.newFixedThreadPool(this.maxthreads); 672 List<Future<Boolean>> taskList = new ArrayList<>(); 673 int serverIndex = 0; 674 for (RegionInfo regionToMove : regionsToMove) { 675 // To move/isolate hbase:meta on a server, it should happen explicitly by Ack mode, hence the 676 // forceMoveRegionByAck = true. 677 if (ack || forceMoveRegionByAck) { 678 Future<Boolean> task = moveRegionsPool.submit(new MoveWithAck(conn, regionToMove, server, 679 regionServers.get(serverIndex), movedRegions)); 680 taskList.add(task); 681 } else { 682 Future<Boolean> task = moveRegionsPool.submit(new MoveWithoutAck(admin, regionToMove, 683 server, regionServers.get(serverIndex), movedRegions)); 684 taskList.add(task); 685 } 686 serverIndex = (serverIndex + 1) % regionServers.size(); 687 } 688 moveRegionsPool.shutdown(); 689 long timeoutInSeconds = regionsToMove.size() 690 * admin.getConfiguration().getLong(MOVE_WAIT_MAX_KEY, DEFAULT_MOVE_WAIT_MAX); 691 waitMoveTasksToFinish(moveRegionsPool, taskList, timeoutInSeconds); 692 } 693 694 private boolean waitTaskToFinish(ExecutorService pool, Future<Boolean> task, String operation) 695 throws TimeoutException, InterruptedException, ExecutionException { 696 pool.shutdown(); 697 try { 698 if (!pool.awaitTermination((long) this.timeout, TimeUnit.SECONDS)) { 699 LOG.warn("Timed out before finishing the " + operation + " operation. Timeout: " 700 + this.timeout + "sec"); 701 pool.shutdownNow(); 702 } 703 } catch (InterruptedException e) { 704 pool.shutdownNow(); 705 Thread.currentThread().interrupt(); 706 } 707 try { 708 return task.get(5, TimeUnit.SECONDS); 709 } catch (InterruptedException e) { 710 LOG.warn("Interrupted while " + operation + " Regions on " + this.hostname, e); 711 throw e; 712 } catch (ExecutionException e) { 713 LOG.error("Error while " + operation + " regions on RegionServer " + this.hostname, e); 714 throw e; 715 } 716 } 717 718 private void waitMoveTasksToFinish(ExecutorService moveRegionsPool, 719 List<Future<Boolean>> taskList, long timeoutInSeconds) throws Exception { 720 try { 721 if (!moveRegionsPool.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS)) { 722 moveRegionsPool.shutdownNow(); 723 } 724 } catch (InterruptedException e) { 725 moveRegionsPool.shutdownNow(); 726 Thread.currentThread().interrupt(); 727 } 728 for (Future<Boolean> future : taskList) { 729 try { 730 // if even after shutdownNow threads are stuck we wait for 5 secs max 731 if (!future.get(5, TimeUnit.SECONDS)) { 732 LOG.error("Was Not able to move region....Exiting Now"); 733 throw new Exception("Could not move region Exception"); 734 } 735 } catch (InterruptedException e) { 736 LOG.error("Interrupted while waiting for Thread to Complete " + e.getMessage(), e); 737 throw e; 738 } catch (ExecutionException e) { 739 boolean ignoreFailure = ignoreRegionMoveFailure(e); 740 if (ignoreFailure) { 741 LOG.debug("Ignore region move failure, it might have been split/merged.", e); 742 } else { 743 LOG.error("Got Exception From Thread While moving region {}", e.getMessage(), e); 744 throw e; 745 } 746 } catch (CancellationException e) { 747 LOG.error("Thread for moving region cancelled. Timeout for cancellation:" + timeoutInSeconds 748 + "secs", e); 749 throw e; 750 } 751 } 752 } 753 754 private boolean ignoreRegionMoveFailure(ExecutionException e) { 755 boolean ignoreFailure = false; 756 if (e.getCause() instanceof UnknownRegionException) { 757 // region does not exist anymore 758 ignoreFailure = true; 759 } else if ( 760 e.getCause() instanceof DoNotRetryRegionException && e.getCause().getMessage() != null 761 && e.getCause().getMessage() 762 .contains(AssignmentManager.UNEXPECTED_STATE_REGION + "state=SPLIT,") 763 ) { 764 // region is recently split 765 ignoreFailure = true; 766 } 767 return ignoreFailure; 768 } 769 770 private ServerName getTargetServer() throws Exception { 771 ServerName server = null; 772 int maxWaitInSeconds = 773 admin.getConfiguration().getInt(SERVERSTART_WAIT_MAX_KEY, DEFAULT_SERVERSTART_WAIT_MAX); 774 long maxWait = EnvironmentEdgeManager.currentTime() + maxWaitInSeconds * 1000; 775 while (EnvironmentEdgeManager.currentTime() < maxWait) { 776 try { 777 List<ServerName> regionServers = new ArrayList<>(); 778 regionServers.addAll(admin.getRegionServers()); 779 // Remove the host Region server from target Region Servers list 780 server = stripServer(regionServers, hostname, port); 781 if (server != null) { 782 break; 783 } else { 784 LOG.warn("Server " + hostname + ":" + port + " is not up yet, waiting"); 785 } 786 } catch (IOException e) { 787 LOG.warn("Could not get list of region servers", e); 788 } 789 Thread.sleep(500); 790 } 791 if (server == null) { 792 LOG.error("Server " + hostname + ":" + port + " is not up. Giving up."); 793 throw new Exception("Server " + hostname + ":" + port + " to load regions not online"); 794 } 795 return server; 796 } 797 798 private List<RegionInfo> readRegionsFromFile(String filename) throws IOException { 799 List<RegionInfo> regions = new ArrayList<>(); 800 File f = new File(filename); 801 if (!f.exists()) { 802 return regions; 803 } 804 try ( 805 DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(f)))) { 806 int numRegions = dis.readInt(); 807 int index = 0; 808 while (index < numRegions) { 809 regions.add(RegionInfo.parseFromOrNull(Bytes.readByteArray(dis))); 810 index++; 811 } 812 } catch (IOException e) { 813 LOG.error("Error while reading regions from file:" + filename, e); 814 throw e; 815 } 816 return regions; 817 } 818 819 /** 820 * Write the number of regions moved in the first line followed by regions moved in subsequent 821 * lines 822 */ 823 private void writeFile(String filename, List<RegionInfo> movedRegions) throws IOException { 824 try (DataOutputStream dos = 825 new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename)))) { 826 dos.writeInt(movedRegions.size()); 827 for (RegionInfo region : movedRegions) { 828 Bytes.writeByteArray(dos, RegionInfo.toByteArray(region)); 829 } 830 } catch (IOException e) { 831 LOG.error("ERROR: Was Not able to write regions moved to output file but moved " 832 + movedRegions.size() + " regions", e); 833 throw e; 834 } 835 } 836 837 private void deleteFile(String filename) { 838 File f = new File(filename); 839 if (f.exists()) { 840 f.delete(); 841 } 842 } 843 844 /** 845 * @param filename The file should have 'host:port' per line 846 * @return List of servers from the file in format 'hostname:port'. 847 */ 848 private List<String> readServersFromFile(String filename) throws IOException { 849 List<String> servers = new ArrayList<>(); 850 if (filename != null) { 851 try { 852 Files.readAllLines(Paths.get(filename)).stream().map(String::trim) 853 .filter(((Predicate<String>) String::isEmpty).negate()).map(String::toLowerCase) 854 .forEach(servers::add); 855 } catch (IOException e) { 856 LOG.error("Exception while reading servers from file,", e); 857 throw e; 858 } 859 } 860 return servers; 861 } 862 863 /** 864 * Designates or excludes the servername whose hostname and port portion matches the list given in 865 * the file. Example:<br> 866 * If you want to designated RSs, suppose designatedFile has RS1, regionServers has RS1, RS2 and 867 * RS3. When we call includeExcludeRegionServers(designatedFile, regionServers, true), RS2 and RS3 868 * are removed from regionServers list so that regions can move to only RS1. If you want to 869 * exclude RSs, suppose excludeFile has RS1, regionServers has RS1, RS2 and RS3. When we call 870 * includeExcludeRegionServers(excludeFile, servers, false), RS1 is removed from regionServers 871 * list so that regions can move to only RS2 and RS3. 872 */ 873 private void includeExcludeRegionServers(String fileName, List<ServerName> regionServers, 874 boolean isInclude) throws IOException { 875 if (fileName != null) { 876 List<String> servers = readServersFromFile(fileName); 877 if (servers.isEmpty()) { 878 LOG.warn("No servers provided in the file: {}." + fileName); 879 return; 880 } 881 Iterator<ServerName> i = regionServers.iterator(); 882 while (i.hasNext()) { 883 String rs = i.next().getServerName(); 884 String rsPort = rs.split(ServerName.SERVERNAME_SEPARATOR)[0].toLowerCase() + ":" 885 + rs.split(ServerName.SERVERNAME_SEPARATOR)[1]; 886 if (isInclude != servers.contains(rsPort)) { 887 i.remove(); 888 } 889 } 890 } 891 } 892 893 /** 894 * Exclude master from list of RSs to move regions to 895 */ 896 private void stripMaster(List<ServerName> regionServers) throws IOException { 897 ServerName master = admin.getClusterMetrics(EnumSet.of(Option.MASTER)).getMasterName(); 898 stripServer(regionServers, master.getHostname(), master.getPort()); 899 } 900 901 /** 902 * Remove the servername whose hostname and port portion matches from the passed array of servers. 903 * Returns as side-effect the servername removed. 904 * @return server removed from list of Region Servers 905 */ 906 private ServerName stripServer(List<ServerName> regionServers, String hostname, int port) { 907 for (Iterator<ServerName> iter = regionServers.iterator(); iter.hasNext();) { 908 ServerName server = iter.next(); 909 if ( 910 server.getAddress().getHostName().equalsIgnoreCase(hostname) 911 && server.getAddress().getPort() == port 912 ) { 913 iter.remove(); 914 return server; 915 } 916 } 917 return null; 918 } 919 920 @Override 921 protected void addOptions() { 922 this.addRequiredOptWithArg("r", "regionserverhost", "region server <hostname>|<hostname:port>"); 923 this.addRequiredOptWithArg("o", "operation", 924 "Expected: load/unload/unload_from_rack/isolate_regions"); 925 this.addOptWithArg("m", "maxthreads", 926 "Define the maximum number of threads to use to unload and reload the regions"); 927 this.addOptWithArg("i", "isolateRegionIds", 928 "Comma separated list of Region IDs hash to isolate on a RegionServer and put region server" 929 + " in draining mode. This option should only be used with '-o isolate_regions'." 930 + " By putting region server in decommission/draining mode, master can't assign any" 931 + " new region on this server. If one or more regions are not found OR failed to isolate" 932 + " successfully, utility will exist without putting RS in draining/decommission mode." 933 + " Ex. --isolateRegionIds id1,id2,id3 OR -i id1,id2,id3"); 934 this.addOptWithArg("x", "excludefile", 935 "File with <hostname:port> per line to exclude as unload targets; default excludes only " 936 + "target host; useful for rack decommisioning."); 937 this.addOptWithArg("d", "designatedfile", 938 "File with <hostname:port> per line as unload targets;" + "default is all online hosts"); 939 this.addOptWithArg("f", "filename", 940 "File to save regions list into unloading, or read from loading; " 941 + "default /tmp/<usernamehostname:port>"); 942 this.addOptNoArg("n", "noack", 943 "Turn on No-Ack mode(default: false) which won't check if region is online on target " 944 + "RegionServer, hence best effort. This is more performant in unloading and loading " 945 + "but might lead to region being unavailable for some time till master reassigns it " 946 + "in case the move failed"); 947 this.addOptWithArg("t", "timeout", "timeout in seconds after which the tool will exit " 948 + "irrespective of whether it finished or not;default Integer.MAX_VALUE"); 949 } 950 951 @Override 952 protected void processOptions(CommandLine cmd) { 953 String hostname = cmd.getOptionValue("r"); 954 rmbuilder = new RegionMoverBuilder(hostname, getConf()); 955 this.loadUnload = cmd.getOptionValue("o").toLowerCase(Locale.ROOT); 956 if (cmd.hasOption('m')) { 957 rmbuilder.maxthreads(Integer.parseInt(cmd.getOptionValue('m'))); 958 } 959 if (this.loadUnload.equals("isolate_regions") && cmd.hasOption("isolateRegionIds")) { 960 rmbuilder 961 .isolateRegionIdArray(Arrays.asList(cmd.getOptionValue("isolateRegionIds").split(","))); 962 } 963 if (cmd.hasOption('n')) { 964 rmbuilder.ack(false); 965 } 966 if (cmd.hasOption('f')) { 967 rmbuilder.filename(cmd.getOptionValue('f')); 968 } 969 if (cmd.hasOption('x')) { 970 rmbuilder.excludeFile(cmd.getOptionValue('x')); 971 } 972 if (cmd.hasOption('d')) { 973 rmbuilder.designatedFile(cmd.getOptionValue('d')); 974 } 975 if (cmd.hasOption('t')) { 976 rmbuilder.timeout(Integer.parseInt(cmd.getOptionValue('t'))); 977 } 978 } 979 980 @Override 981 protected int doWork() throws Exception { 982 boolean success; 983 try (RegionMover rm = rmbuilder.build()) { 984 if (loadUnload.equalsIgnoreCase("load")) { 985 success = rm.load(); 986 } else if (loadUnload.equalsIgnoreCase("unload")) { 987 success = rm.unload(); 988 } else if (loadUnload.equalsIgnoreCase("unload_from_rack")) { 989 success = rm.unloadFromRack(); 990 } else if (loadUnload.equalsIgnoreCase("isolate_regions")) { 991 if (rm.isolateRegionIdArray != null && !rm.isolateRegionIdArray.isEmpty()) { 992 success = rm.isolateRegions(); 993 } else { 994 LOG.error("Missing -i/--isolate_regions option with '-o isolate_regions' option"); 995 LOG.error("Use -h or --help for usage instructions"); 996 printUsage(); 997 success = false; 998 } 999 } else { 1000 printUsage(); 1001 success = false; 1002 } 1003 } 1004 return (success ? 0 : 1); 1005 } 1006 1007 public static void main(String[] args) { 1008 try (RegionMover mover = new RegionMover()) { 1009 mover.doStaticMain(args); 1010 } 1011 } 1012}