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 */ 019 020package org.apache.hadoop.hbase.util; 021 022import java.io.BufferedInputStream; 023import java.io.BufferedOutputStream; 024import java.io.Closeable; 025import java.io.DataInputStream; 026import java.io.DataOutputStream; 027import java.io.File; 028import java.io.FileInputStream; 029import java.io.FileOutputStream; 030import java.io.IOException; 031import java.nio.file.Files; 032import java.nio.file.Paths; 033import java.util.ArrayList; 034import java.util.Collections; 035import java.util.EnumSet; 036import java.util.HashSet; 037import java.util.Iterator; 038import java.util.List; 039import java.util.Locale; 040import java.util.Set; 041import java.util.concurrent.CancellationException; 042import java.util.concurrent.ExecutionException; 043import java.util.concurrent.ExecutorService; 044import java.util.concurrent.Executors; 045import java.util.concurrent.Future; 046import java.util.concurrent.TimeUnit; 047import java.util.concurrent.TimeoutException; 048import java.util.function.Predicate; 049import org.apache.commons.io.IOUtils; 050import org.apache.hadoop.conf.Configuration; 051import org.apache.hadoop.hbase.ClusterMetrics.Option; 052import org.apache.hadoop.hbase.HBaseConfiguration; 053import org.apache.hadoop.hbase.HConstants; 054import org.apache.hadoop.hbase.ServerName; 055import org.apache.hadoop.hbase.UnknownRegionException; 056import org.apache.hadoop.hbase.client.Admin; 057import org.apache.hadoop.hbase.client.Connection; 058import org.apache.hadoop.hbase.client.ConnectionFactory; 059import org.apache.hadoop.hbase.client.DoNotRetryRegionException; 060import org.apache.hadoop.hbase.client.RegionInfo; 061import org.apache.hadoop.hbase.master.assignment.AssignmentManager; 062import org.apache.yetus.audience.InterfaceAudience; 063import org.slf4j.Logger; 064import org.slf4j.LoggerFactory; 065 066import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 067import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils; 068 069/** 070 * Tool for loading/unloading regions to/from given regionserver This tool can be run from Command 071 * line directly as a utility. Supports Ack/No Ack mode for loading/unloading operations.Ack mode 072 * acknowledges if regions are online after movement while noAck mode is best effort mode that 073 * improves performance but will still move on if region is stuck/not moved. Motivation behind noAck 074 * mode being RS shutdown where even if a Region is stuck, upon shutdown master will move it 075 * anyways. This can also be used by constructiong an Object using the builder and then calling 076 * {@link #load()} or {@link #unload()} methods for the desired operations. 077 */ 078@InterfaceAudience.Public 079public class RegionMover extends AbstractHBaseTool implements Closeable { 080 public static final String MOVE_RETRIES_MAX_KEY = "hbase.move.retries.max"; 081 public static final String MOVE_WAIT_MAX_KEY = "hbase.move.wait.max"; 082 public static final String SERVERSTART_WAIT_MAX_KEY = "hbase.serverstart.wait.max"; 083 public static final int DEFAULT_MOVE_RETRIES_MAX = 5; 084 public static final int DEFAULT_MOVE_WAIT_MAX = 60; 085 public static final int DEFAULT_SERVERSTART_WAIT_MAX = 180; 086 087 private static final Logger LOG = LoggerFactory.getLogger(RegionMover.class); 088 089 private RegionMoverBuilder rmbuilder; 090 private boolean ack = true; 091 private int maxthreads = 1; 092 private int timeout; 093 private String loadUnload; 094 private String hostname; 095 private String filename; 096 private String excludeFile; 097 private String designatedFile; 098 private int port; 099 private Connection conn; 100 private Admin admin; 101 102 private RegionMover(RegionMoverBuilder builder) throws IOException { 103 this.hostname = builder.hostname; 104 this.filename = builder.filename; 105 this.excludeFile = builder.excludeFile; 106 this.designatedFile = builder.designatedFile; 107 this.maxthreads = builder.maxthreads; 108 this.ack = builder.ack; 109 this.port = builder.port; 110 this.timeout = builder.timeout; 111 setConf(builder.conf); 112 this.conn = ConnectionFactory.createConnection(conf); 113 this.admin = conn.getAdmin(); 114 } 115 116 private RegionMover() { 117 } 118 119 @Override 120 public void close() { 121 IOUtils.closeQuietly(this.admin); 122 IOUtils.closeQuietly(this.conn); 123 } 124 125 /** 126 * Builder for Region mover. Use the {@link #build()} method to create RegionMover object. Has 127 * {@link #filename(String)}, {@link #excludeFile(String)}, {@link #maxthreads(int)}, 128 * {@link #ack(boolean)}, {@link #timeout(int)}, {@link #designatedFile(String)} methods to set 129 * the corresponding options. 130 */ 131 public static class RegionMoverBuilder { 132 private boolean ack = true; 133 private int maxthreads = 1; 134 private int timeout = Integer.MAX_VALUE; 135 private String hostname; 136 private String filename; 137 private String excludeFile = null; 138 private String designatedFile = null; 139 private String defaultDir = System.getProperty("java.io.tmpdir"); 140 @InterfaceAudience.Private 141 final int port; 142 private final Configuration conf; 143 144 public RegionMoverBuilder(String hostname) { 145 this(hostname, createConf()); 146 } 147 148 /** 149 * Creates a new configuration and sets region mover specific overrides 150 */ 151 private static Configuration createConf() { 152 Configuration conf = HBaseConfiguration.create(); 153 conf.setInt("hbase.client.prefetch.limit", 1); 154 conf.setInt("hbase.client.pause", 500); 155 conf.setInt("hbase.client.retries.number", 100); 156 return conf; 157 } 158 159 /** 160 * @param hostname Hostname to unload regions from or load regions to. Can be either hostname 161 * or hostname:port. 162 * @param conf Configuration object 163 */ 164 public RegionMoverBuilder(String hostname, Configuration conf) { 165 String[] splitHostname = hostname.toLowerCase().split(":"); 166 this.hostname = splitHostname[0]; 167 if (splitHostname.length == 2) { 168 this.port = Integer.parseInt(splitHostname[1]); 169 } else { 170 this.port = conf.getInt(HConstants.REGIONSERVER_PORT, HConstants.DEFAULT_REGIONSERVER_PORT); 171 } 172 this.filename = defaultDir + File.separator + System.getProperty("user.name") + this.hostname 173 + ":" + Integer.toString(this.port); 174 this.conf = conf; 175 } 176 177 /** 178 * Path of file where regions will be written to during unloading/read from during loading 179 * @param filename 180 * @return RegionMoverBuilder object 181 */ 182 public RegionMoverBuilder filename(String filename) { 183 this.filename = filename; 184 return this; 185 } 186 187 /** 188 * Set the max number of threads that will be used to move regions 189 */ 190 public RegionMoverBuilder maxthreads(int threads) { 191 this.maxthreads = threads; 192 return this; 193 } 194 195 /** 196 * Path of file containing hostnames to be excluded during region movement. Exclude file should 197 * have 'host:port' per line. Port is mandatory here as we can have many RS running on a single 198 * host. 199 */ 200 public RegionMoverBuilder excludeFile(String excludefile) { 201 this.excludeFile = excludefile; 202 return this; 203 } 204 205 /** 206 * Set the designated file. Designated file contains hostnames where region moves. Designated 207 * file should have 'host:port' per line. Port is mandatory here as we can have many RS running 208 * on a single host. 209 * @param designatedFile The designated file 210 * @return RegionMoverBuilder object 211 */ 212 public RegionMoverBuilder designatedFile(String designatedFile) { 213 this.designatedFile = designatedFile; 214 return this; 215 } 216 217 /** 218 * Set ack/noAck mode. 219 * <p> 220 * In ack mode regions are acknowledged before and after moving and the move is retried 221 * hbase.move.retries.max times, if unsuccessful we quit with exit code 1.No Ack mode is a best 222 * effort mode,each region movement is tried once.This can be used during graceful shutdown as 223 * even if we have a stuck region,upon shutdown it'll be reassigned anyway. 224 * <p> 225 * @param ack 226 * @return RegionMoverBuilder object 227 */ 228 public RegionMoverBuilder ack(boolean ack) { 229 this.ack = ack; 230 return this; 231 } 232 233 /** 234 * Set the timeout for Load/Unload operation in seconds.This is a global timeout,threadpool for 235 * movers also have a separate time which is hbase.move.wait.max * number of regions to 236 * load/unload 237 * @param timeout in seconds 238 * @return RegionMoverBuilder object 239 */ 240 public RegionMoverBuilder timeout(int timeout) { 241 this.timeout = timeout; 242 return this; 243 } 244 245 /** 246 * This method builds the appropriate RegionMover object which can then be used to load/unload 247 * using load and unload methods 248 * @return RegionMover object 249 */ 250 public RegionMover build() throws IOException { 251 return new RegionMover(this); 252 } 253 } 254 255 /** 256 * Loads the specified {@link #hostname} with regions listed in the {@link #filename} RegionMover 257 * Object has to be created using {@link #RegionMover(RegionMoverBuilder)} 258 * @return true if loading succeeded, false otherwise 259 */ 260 public boolean load() throws ExecutionException, InterruptedException, TimeoutException { 261 ExecutorService loadPool = Executors.newFixedThreadPool(1); 262 Future<Boolean> loadTask = loadPool.submit(() -> { 263 try { 264 List<RegionInfo> regionsToMove = readRegionsFromFile(filename); 265 if (regionsToMove.isEmpty()) { 266 LOG.info("No regions to load.Exiting"); 267 return true; 268 } 269 loadRegions(regionsToMove); 270 } catch (Exception e) { 271 LOG.error("Error while loading regions to " + hostname, e); 272 return false; 273 } 274 return true; 275 }); 276 return waitTaskToFinish(loadPool, loadTask, "loading"); 277 } 278 279 private void loadRegions(List<RegionInfo> regionsToMove) 280 throws Exception { 281 ServerName server = getTargetServer(); 282 List<RegionInfo> movedRegions = Collections.synchronizedList(new ArrayList<>()); 283 LOG.info( 284 "Moving " + regionsToMove.size() + " regions to " + server + " using " + this.maxthreads 285 + " threads.Ack mode:" + this.ack); 286 287 final ExecutorService moveRegionsPool = Executors.newFixedThreadPool(this.maxthreads); 288 List<Future<Boolean>> taskList = new ArrayList<>(); 289 int counter = 0; 290 while (counter < regionsToMove.size()) { 291 RegionInfo region = regionsToMove.get(counter); 292 ServerName currentServer = MoveWithAck.getServerNameForRegion(region, admin, conn); 293 if (currentServer == null) { 294 LOG.warn( 295 "Could not get server for Region:" + region.getRegionNameAsString() + " moving on"); 296 counter++; 297 continue; 298 } else if (server.equals(currentServer)) { 299 LOG.info( 300 "Region " + region.getRegionNameAsString() + " is already on target server=" + server); 301 counter++; 302 continue; 303 } 304 if (ack) { 305 Future<Boolean> task = moveRegionsPool 306 .submit(new MoveWithAck(conn, region, currentServer, server, movedRegions)); 307 taskList.add(task); 308 } else { 309 Future<Boolean> task = moveRegionsPool 310 .submit(new MoveWithoutAck(admin, region, currentServer, server, movedRegions)); 311 taskList.add(task); 312 } 313 counter++; 314 } 315 316 moveRegionsPool.shutdown(); 317 long timeoutInSeconds = regionsToMove.size() * admin.getConfiguration() 318 .getLong(MOVE_WAIT_MAX_KEY, DEFAULT_MOVE_WAIT_MAX); 319 waitMoveTasksToFinish(moveRegionsPool, taskList, timeoutInSeconds); 320 } 321 322 /** 323 * Unload regions from given {@link #hostname} using ack/noAck mode and {@link #maxthreads}.In 324 * noAck mode we do not make sure that region is successfully online on the target region 325 * server,hence it is best effort.We do not unload regions to hostnames given in 326 * {@link #excludeFile}. If designatedFile is present with some contents, we will unload regions 327 * to hostnames provided in {@link #designatedFile} 328 * @return true if unloading succeeded, false otherwise 329 */ 330 public boolean unload() throws InterruptedException, ExecutionException, TimeoutException { 331 deleteFile(this.filename); 332 ExecutorService unloadPool = Executors.newFixedThreadPool(1); 333 Future<Boolean> unloadTask = unloadPool.submit(() -> { 334 List<RegionInfo> movedRegions = Collections.synchronizedList(new ArrayList<>()); 335 try { 336 // Get Online RegionServers 337 List<ServerName> regionServers = new ArrayList<>(); 338 regionServers.addAll(admin.getRegionServers()); 339 // Remove the host Region server from target Region Servers list 340 ServerName server = stripServer(regionServers, hostname, port); 341 if (server == null) { 342 LOG.info("Could not find server '{}:{}' in the set of region servers. giving up.", 343 hostname, port); 344 LOG.debug("List of region servers: {}", regionServers); 345 return false; 346 } 347 // Remove RS not present in the designated file 348 includeExcludeRegionServers(designatedFile, regionServers, true); 349 350 // Remove RS present in the exclude file 351 includeExcludeRegionServers(excludeFile, regionServers, false); 352 353 // Remove decommissioned RS 354 Set<ServerName> decommissionedRS = new HashSet<>(admin.listDecommissionedRegionServers()); 355 if (CollectionUtils.isNotEmpty(decommissionedRS)) { 356 regionServers.removeIf(decommissionedRS::contains); 357 LOG.debug("Excluded RegionServers from unloading regions to because they " + 358 "are marked as decommissioned. Servers: {}", decommissionedRS); 359 } 360 361 stripMaster(regionServers); 362 if (regionServers.isEmpty()) { 363 LOG.warn("No Regions were moved - no servers available"); 364 return false; 365 } 366 unloadRegions(server, regionServers, movedRegions); 367 } catch (Exception e) { 368 LOG.error("Error while unloading regions ", e); 369 return false; 370 } finally { 371 if (movedRegions != null) { 372 writeFile(filename, movedRegions); 373 } 374 } 375 return true; 376 }); 377 return waitTaskToFinish(unloadPool, unloadTask, "unloading"); 378 } 379 380 private void unloadRegions(ServerName server, List<ServerName> regionServers, 381 List<RegionInfo> movedRegions) throws Exception { 382 while (true) { 383 List<RegionInfo> regionsToMove = admin.getRegions(server); 384 regionsToMove.removeAll(movedRegions); 385 if (regionsToMove.isEmpty()) { 386 LOG.info("No Regions to move....Quitting now"); 387 break; 388 } 389 LOG.info("Moving {} regions from {} to {} servers using {} threads .Ack Mode: {}", 390 regionsToMove.size(), this.hostname, regionServers.size(), this.maxthreads, ack); 391 final ExecutorService moveRegionsPool = Executors.newFixedThreadPool(this.maxthreads); 392 List<Future<Boolean>> taskList = new ArrayList<>(); 393 int serverIndex = 0; 394 for (RegionInfo regionToMove : regionsToMove) { 395 if (ack) { 396 Future<Boolean> task = moveRegionsPool.submit( 397 new MoveWithAck(conn, regionToMove, server, regionServers.get(serverIndex), 398 movedRegions)); 399 taskList.add(task); 400 } else { 401 Future<Boolean> task = moveRegionsPool.submit( 402 new MoveWithoutAck(admin, regionToMove, server, regionServers.get(serverIndex), 403 movedRegions)); 404 taskList.add(task); 405 } 406 serverIndex = (serverIndex + 1) % regionServers.size(); 407 } 408 moveRegionsPool.shutdown(); 409 long timeoutInSeconds = regionsToMove.size() * admin.getConfiguration() 410 .getLong(MOVE_WAIT_MAX_KEY, DEFAULT_MOVE_WAIT_MAX); 411 waitMoveTasksToFinish(moveRegionsPool, taskList, timeoutInSeconds); 412 } 413 } 414 415 private boolean waitTaskToFinish(ExecutorService pool, Future<Boolean> task, String operation) 416 throws TimeoutException, InterruptedException, ExecutionException { 417 pool.shutdown(); 418 try { 419 if (!pool.awaitTermination((long) this.timeout, TimeUnit.SECONDS)) { 420 LOG.warn( 421 "Timed out before finishing the " + operation + " operation. Timeout: " + this.timeout 422 + "sec"); 423 pool.shutdownNow(); 424 } 425 } catch (InterruptedException e) { 426 pool.shutdownNow(); 427 Thread.currentThread().interrupt(); 428 } 429 try { 430 return task.get(5, TimeUnit.SECONDS); 431 } catch (InterruptedException e) { 432 LOG.warn("Interrupted while " + operation + " Regions on " + this.hostname, e); 433 throw e; 434 } catch (ExecutionException e) { 435 LOG.error("Error while " + operation + " regions on RegionServer " + this.hostname, e); 436 throw e; 437 } 438 } 439 440 private void waitMoveTasksToFinish(ExecutorService moveRegionsPool, 441 List<Future<Boolean>> taskList, long timeoutInSeconds) throws Exception { 442 try { 443 if (!moveRegionsPool.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS)) { 444 moveRegionsPool.shutdownNow(); 445 } 446 } catch (InterruptedException e) { 447 moveRegionsPool.shutdownNow(); 448 Thread.currentThread().interrupt(); 449 } 450 for (Future<Boolean> future : taskList) { 451 try { 452 // if even after shutdownNow threads are stuck we wait for 5 secs max 453 if (!future.get(5, TimeUnit.SECONDS)) { 454 LOG.error("Was Not able to move region....Exiting Now"); 455 throw new Exception("Could not move region Exception"); 456 } 457 } catch (InterruptedException e) { 458 LOG.error("Interrupted while waiting for Thread to Complete " + e.getMessage(), e); 459 throw e; 460 } catch (ExecutionException e) { 461 boolean ignoreFailure = ignoreRegionMoveFailure(e); 462 if (ignoreFailure) { 463 LOG.debug("Ignore region move failure, it might have been split/merged.", e); 464 } else { 465 LOG.error("Got Exception From Thread While moving region {}", e.getMessage(), e); 466 throw e; 467 } 468 } catch (CancellationException e) { 469 LOG.error("Thread for moving region cancelled. Timeout for cancellation:" + timeoutInSeconds 470 + "secs", e); 471 throw e; 472 } 473 } 474 } 475 476 private boolean ignoreRegionMoveFailure(ExecutionException e) { 477 boolean ignoreFailure = false; 478 if (e.getCause() instanceof UnknownRegionException) { 479 // region does not exist anymore 480 ignoreFailure = true; 481 } else if (e.getCause() instanceof DoNotRetryRegionException 482 && e.getCause().getMessage() != null && e.getCause().getMessage() 483 .contains(AssignmentManager.UNEXPECTED_STATE_REGION + "state=SPLIT,")) { 484 // region is recently split 485 ignoreFailure = true; 486 } 487 return ignoreFailure; 488 } 489 490 private ServerName getTargetServer() throws Exception { 491 ServerName server = null; 492 int maxWaitInSeconds = 493 admin.getConfiguration().getInt(SERVERSTART_WAIT_MAX_KEY, DEFAULT_SERVERSTART_WAIT_MAX); 494 long maxWait = EnvironmentEdgeManager.currentTime() + maxWaitInSeconds * 1000; 495 while (EnvironmentEdgeManager.currentTime() < maxWait) { 496 try { 497 List<ServerName> regionServers = new ArrayList<>(); 498 regionServers.addAll(admin.getRegionServers()); 499 // Remove the host Region server from target Region Servers list 500 server = stripServer(regionServers, hostname, port); 501 if (server != null) { 502 break; 503 } else { 504 LOG.warn("Server " + hostname + ":" + port + " is not up yet, waiting"); 505 } 506 } catch (IOException e) { 507 LOG.warn("Could not get list of region servers", e); 508 } 509 Thread.sleep(500); 510 } 511 if (server == null) { 512 LOG.error("Server " + hostname + ":" + port + " is not up. Giving up."); 513 throw new Exception("Server " + hostname + ":" + port + " to load regions not online"); 514 } 515 return server; 516 } 517 518 private List<RegionInfo> readRegionsFromFile(String filename) throws IOException { 519 List<RegionInfo> regions = new ArrayList<>(); 520 File f = new File(filename); 521 if (!f.exists()) { 522 return regions; 523 } 524 try (DataInputStream dis = new DataInputStream( 525 new BufferedInputStream(new FileInputStream(f)))) { 526 int numRegions = dis.readInt(); 527 int index = 0; 528 while (index < numRegions) { 529 regions.add(RegionInfo.parseFromOrNull(Bytes.readByteArray(dis))); 530 index++; 531 } 532 } catch (IOException e) { 533 LOG.error("Error while reading regions from file:" + filename, e); 534 throw e; 535 } 536 return regions; 537 } 538 539 /** 540 * Write the number of regions moved in the first line followed by regions moved in subsequent 541 * lines 542 */ 543 private void writeFile(String filename, List<RegionInfo> movedRegions) throws IOException { 544 try (DataOutputStream dos = new DataOutputStream( 545 new BufferedOutputStream(new FileOutputStream(filename)))) { 546 dos.writeInt(movedRegions.size()); 547 for (RegionInfo region : movedRegions) { 548 Bytes.writeByteArray(dos, RegionInfo.toByteArray(region)); 549 } 550 } catch (IOException e) { 551 LOG.error( 552 "ERROR: Was Not able to write regions moved to output file but moved " + movedRegions 553 .size() + " regions", e); 554 throw e; 555 } 556 } 557 558 private void deleteFile(String filename) { 559 File f = new File(filename); 560 if (f.exists()) { 561 f.delete(); 562 } 563 } 564 565 /** 566 * @param filename The file should have 'host:port' per line 567 * @return List of servers from the file in format 'hostname:port'. 568 */ 569 private List<String> readServersFromFile(String filename) throws IOException { 570 List<String> servers = new ArrayList<>(); 571 if (filename != null) { 572 try { 573 Files.readAllLines(Paths.get(filename)).stream().map(String::trim) 574 .filter(((Predicate<String>) String::isEmpty).negate()).map(String::toLowerCase) 575 .forEach(servers::add); 576 } catch (IOException e) { 577 LOG.error("Exception while reading servers from file,", e); 578 throw e; 579 } 580 } 581 return servers; 582 } 583 584 /** 585 * Designates or excludes the servername whose hostname and port portion matches the list given 586 * in the file. 587 * Example:<br> 588 * If you want to designated RSs, suppose designatedFile has RS1, regionServers has RS1, RS2 and 589 * RS3. When we call includeExcludeRegionServers(designatedFile, regionServers, true), RS2 and 590 * RS3 are removed from regionServers list so that regions can move to only RS1. 591 * If you want to exclude RSs, suppose excludeFile has RS1, regionServers has RS1, RS2 and RS3. 592 * When we call includeExcludeRegionServers(excludeFile, servers, false), RS1 is removed from 593 * regionServers list so that regions can move to only RS2 and RS3. 594 */ 595 private void includeExcludeRegionServers(String fileName, List<ServerName> regionServers, 596 boolean isInclude) throws IOException { 597 if (fileName != null) { 598 List<String> servers = readServersFromFile(fileName); 599 if (servers.isEmpty()) { 600 LOG.warn("No servers provided in the file: {}." + fileName); 601 return; 602 } 603 Iterator<ServerName> i = regionServers.iterator(); 604 while (i.hasNext()) { 605 String rs = i.next().getServerName(); 606 String rsPort = rs.split(ServerName.SERVERNAME_SEPARATOR)[0].toLowerCase() + ":" + rs 607 .split(ServerName.SERVERNAME_SEPARATOR)[1]; 608 if (isInclude != servers.contains(rsPort)) { 609 i.remove(); 610 } 611 } 612 } 613 } 614 615 /** 616 * Exclude master from list of RSs to move regions to 617 */ 618 private void stripMaster(List<ServerName> regionServers) throws IOException { 619 ServerName master = admin.getClusterMetrics(EnumSet.of(Option.MASTER)).getMasterName(); 620 stripServer(regionServers, master.getHostname(), master.getPort()); 621 } 622 623 /** 624 * Remove the servername whose hostname and port portion matches from the passed array of servers. 625 * Returns as side-effect the servername removed. 626 * @return server removed from list of Region Servers 627 */ 628 private ServerName stripServer(List<ServerName> regionServers, String hostname, int port) { 629 for (Iterator<ServerName> iter = regionServers.iterator(); iter.hasNext();) { 630 ServerName server = iter.next(); 631 if (server.getAddress().getHostname().equalsIgnoreCase(hostname) && 632 server.getAddress().getPort() == port) { 633 iter.remove(); 634 return server; 635 } 636 } 637 return null; 638 } 639 640 @Override 641 protected void addOptions() { 642 this.addRequiredOptWithArg("r", "regionserverhost", "region server <hostname>|<hostname:port>"); 643 this.addRequiredOptWithArg("o", "operation", "Expected: load/unload"); 644 this.addOptWithArg("m", "maxthreads", 645 "Define the maximum number of threads to use to unload and reload the regions"); 646 this.addOptWithArg("x", "excludefile", 647 "File with <hostname:port> per line to exclude as unload targets; default excludes only " 648 + "target host; useful for rack decommisioning."); 649 this.addOptWithArg("d","designatedfile","File with <hostname:port> per line as unload targets;" 650 + "default is all online hosts"); 651 this.addOptWithArg("f", "filename", 652 "File to save regions list into unloading, or read from loading; " 653 + "default /tmp/<usernamehostname:port>"); 654 this.addOptNoArg("n", "noack", 655 "Turn on No-Ack mode(default: false) which won't check if region is online on target " 656 + "RegionServer, hence best effort. This is more performant in unloading and loading " 657 + "but might lead to region being unavailable for some time till master reassigns it " 658 + "in case the move failed"); 659 this.addOptWithArg("t", "timeout", "timeout in seconds after which the tool will exit " 660 + "irrespective of whether it finished or not;default Integer.MAX_VALUE"); 661 } 662 663 @Override 664 protected void processOptions(CommandLine cmd) { 665 String hostname = cmd.getOptionValue("r"); 666 rmbuilder = new RegionMoverBuilder(hostname); 667 if (cmd.hasOption('m')) { 668 rmbuilder.maxthreads(Integer.parseInt(cmd.getOptionValue('m'))); 669 } 670 if (cmd.hasOption('n')) { 671 rmbuilder.ack(false); 672 } 673 if (cmd.hasOption('f')) { 674 rmbuilder.filename(cmd.getOptionValue('f')); 675 } 676 if (cmd.hasOption('x')) { 677 rmbuilder.excludeFile(cmd.getOptionValue('x')); 678 } 679 if (cmd.hasOption('d')) { 680 rmbuilder.designatedFile(cmd.getOptionValue('d')); 681 } 682 if (cmd.hasOption('t')) { 683 rmbuilder.timeout(Integer.parseInt(cmd.getOptionValue('t'))); 684 } 685 this.loadUnload = cmd.getOptionValue("o").toLowerCase(Locale.ROOT); 686 } 687 688 @Override 689 protected int doWork() throws Exception { 690 boolean success; 691 try (RegionMover rm = rmbuilder.build()) { 692 if (loadUnload.equalsIgnoreCase("load")) { 693 success = rm.load(); 694 } else if (loadUnload.equalsIgnoreCase("unload")) { 695 success = rm.unload(); 696 } else { 697 printUsage(); 698 success = false; 699 } 700 } 701 return (success ? 0 : 1); 702 } 703 704 public static void main(String[] args) { 705 try (RegionMover mover = new RegionMover()) { 706 mover.doStaticMain(args); 707 } 708 } 709}